<?php

namespace App\Kuafu\Order\Service;

use App\Exceptions\BaseException;
use App\Kuafu\Activity\Enum\ActivityEnum;
use App\Kuafu\Activity\Model\ActivityProductMappingModel;
use App\Kuafu\Activity\Service\ActivityService;
use App\Kuafu\Base\Enum\DBEnum;
use App\Kuafu\Base\Model\BaseModel;
use App\Kuafu\Coupon\Enum\CouponEnum;
use App\Kuafu\Coupon\Model\CouponModel;
use App\Kuafu\Coupon\Service\CouponService;
use App\Kuafu\Coupon\Service\PosEnableCouponService;
use App\Kuafu\CouponExt\Enum\CouponExtEnum;
use App\Kuafu\CouponExt\Exceptions\CouponExtException;
use App\Kuafu\CouponPackage\Enum\CouponPackageEnum;
use App\Kuafu\CouponPackage\Model\CouponPackageMappingModel;
use App\Kuafu\CouponPackage\Model\CouponPackageModel;
use App\Kuafu\CouponPackage\Model\CouponPackageOrderModel;
use App\Kuafu\CouponPackage\Service\CouponPackageEnableCouponService;
use App\Kuafu\CouponPackage\Service\CouponPackageService;
use App\Kuafu\CustomerCoupon\Model\CustomerCouponModel;
use App\Kuafu\CustomerCoupon\Service\CustomerCouponService;
use App\Kuafu\CustomPayment\Enum\CustomPaymentCodeEnum;
use App\Kuafu\CustomPayment\Enum\CustomPaymentEnum;
use App\Kuafu\CustomPayment\Service\CustomPaymentService;
use App\Kuafu\Order\Enum\CodeEnum;
use App\Kuafu\Order\Enum\OrderCodeEnum;
use App\Kuafu\Order\Enum\OrderInfoEnum;
use App\Kuafu\Order\Exceptions\OrderException;
use App\Kuafu\Order\Model\OrderCouponSplitModel;
use App\Kuafu\Pay\Model\PayStreamModel;
use App\Kuafu\Pay\Service\PayService;
use App\Kuafu\Product\Enum\CodeEnum as ProductCode;
use App\Kuafu\Product\Model\Product;
use App\Kuafu\Product\Service\ProductShopService;
use App\Kuafu\Shop\Model\Shop;
use App\Kuafu\Shop\Service\ShopArchivesService;
use App\Kuafu\Shop\Service\ShopDistributionService;
use App\Kuafu\Shop\Service\ShopService;
use App\Kuafu\User\Exceptions\UserException;
use App\Kuafu\User\Service\AuthService;
use App\Library\SnowFlake\SnowFlake;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Support\Carbon;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;

/**
 * 订单计价服务
 *
 * Class OrderOperateService
 * @package App\Kuafu\Order\Service
 */
class OrderCalcService extends OrderBaseSplitService
{
    public function __construct($brandId = 0, $shopId = 0, $orderId = '')
    {
        parent::__construct($brandId, $shopId, $orderId);
        $this->menuId = ShopService::getShopInfo($shopId)->menuId ?? 0;
    }

    /**
     * 菜单 ID
     *
     * @var int
     */
    private int $menuId = 0;

    /**
     * @var int 订单来源
     */
    private int $sourceType = 1;

    /**
     * @var array ProductIds POS 传过来的商品 ID 集合
     */
    private array $productIds = [];

    /**
     * @var array POS 传过来的 Detail
     */
    private array $orderPosDetail = [];

    /**
     * @var array POS 传过来的总金额
     */
    private array $orderPosTotal = [];

    /**
     * @var array POS 传过来的整单优惠
     */
    private array $orderPosDiscount = [];

    /**
     * @var array POS 传过来的三方优惠券
     */
    private array $orderPosCouponExt = [];

    /**
     * @var array POS 传过来的需要退回的三方优惠券
     */
    private array $orderPosCouponExtRefund = [];

    /**
     * @var array 传入进来用户自己的优惠券
     */
    private array $orderUserCoupon = [];

    /**
     * @var array 传入进来 [pos/小程序] 匿名优惠券
     */
    private array $orderPosCoupon = [];

    /**
     * @var int 券包 ID
     */
    private int $couponPackageId = 0;

    /**
     * @var array 券包信息
     */
    private array $couponPackageInfo = [];

    /**
     * @var array 券包对应的 可用 券列表
     */
    private array $couponPackageCouponList = [];

    /**
     * @var array  券包对应 不可用 券列表
     */
    private array $couponUnablePackageCouponList = [];

    /**
     * @var array 自己生成的 Detail
     */
    private array $orderFilterOrderDetail = [];

    /**
     * @var array 数据库的商品原数据 ["id_menu" => [对应数据]]
     */
    private array $orderProductDetail = [];

    /**
     * @var string 订单原价
     */
    private string $orderOriginalPrice = '0';

    /**
     * @var string 订单剩余应收
     */
    private string $orderShouldPrice = '0';

    /**
     * @var string 订单总应收
     */
    private string $orderTotalShouldPrice = '0';

    /**
     * @var string 订单总优惠价格
     */
    private string $orderDiscountPrice = '0';

    /**
     * @var string 优惠券总优惠的金额
     */
    private string $couponPrice = '0';

    /**
     * @var string 所有优惠券成本收入
     */
    private string $couponCostPrice = '0';

    /**
     * @var string 所有优惠券优惠了的总金额（面值-成本）
     */
    private string $couponPriceSubCost = '0';

    /**
     * @var string 三方优惠券优惠券总优惠了的总金额
     */
    private string $extDiscountPrice = '0';

    /**
     * @var int 用户 Id
     */
    private int $userId = 0;

    /**
     * @var array 需要核销的优惠券
     */
    private array $needCheckExtCoupon = [];

    /**
     * @var array 需要退款的三方优惠券
     */
    private array $needRefundExtCoupon = [];

    /**
     * @var array 自定义支付列表
     */
    private array $orderCustomPaymentList = [];

    /**
     * @var array 订单支付列表[... 'paymentType' => Code::ORDER_PAYMENT_TYPE_FIAT, 'isNew' => true/false]
     */
    private array $orderPaymentList = [];

    /**
     * @var array 退款的自定义支付列表
     */
    private array $orderRefundCustomPaymentList = [];

    /**
     * @var array 退款的用户支付宝微信
     */
    private array $orderUserSidePaymentList = [];

    /**
     * @var array 退券的列表
     */
    private array $orderRefundPosCouponList = [];

    /**
     * @var array 订单退款列表 [... 'paymentType' => Code::ORDER_PAYMENT_TYPE_FIAT, 'isNew' => true/false]
     */
    private array $orderRefundPaymentList = [];

    /**
     * @var int 是否自配送 - 逐渐不用
     */
    private int $isSelfDelivery = BaseModel::SWITCH_OFF;

    /**
     * @var int 配送类型 0 默认 1 自配送 2 渠道类型
     */
    private int $deliveryType = CodeEnum::DELIVERY_TYPE_DEFAULT;

    private array $postData = [];

    /**
     * 夸父商品券优惠金额
     *
     * @var string
     */
    private string $kfExchangeDiscount = '0';

    /**
     * 夸父非商品券优惠金额
     *
     * @var string
     */
    private string $kfNonExchangeDiscountPrice = '0';

    /**
     * 初始化 订单 detail V3
     *
     * @param array $data
     * @return $this
     * @throws OrderException
     */
    public function initOrderDetailV3(array $data): OrderCalcService
    {
        $detail       = $data['detail'];
        $refundCoupon = $data['refundCoupon'];
        $orderEvent   = $data['orderEvent'];


        if (empty($detail) && 0 == count($refundCoupon) && 0 == count($data["refundPosCouponList"]) && $orderEvent == CodeEnum::ORDER_PAY_EVENT_ORDER) {
            throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR, '提交数据有误');
        }

        $data = [];
        foreach ($detail as $one) {
            if (empty($one['num'])) {
                throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR, '菜品数量有误');
            }

            $postData = [
                'id'                => $one['id'] ?? 0,
                'productId'         => $one['productId'] ?? 0,
                'num'               => $one['num'],
                'originalUnitPrice' => $one['originalUnitPrice'] ?? 0,
                'originalPrice'     => $one['originalPrice'] ?? 0,
                'discountPrice'     => $one['discountPrice'] ?? 0,
                'freeNum'           => $one['freeNum'] ?? 0,
                'discountType'      => $one['discountType'] ?? 0,
                'discountNum'       => $one['discountNum'] ?? 0,
                'refundNum'         => $one['refundNum'] ?? 0,
                'remark'            => $one['remark'] ?? '',
            ];

            if ($postData['freeNum'] > $postData['num']) {
                throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR, 'POS-赠菜数量有误');
            }

            $data[] = $postData;
        }

        // 检出所有商品
        $this->orderPosDetail = $data;
        $this->productIds     = array_column($data, 'productId');
        $this->setOrderProductDetail($orderEvent);

        return $this;
    }

    /**
     * 校验赠品库存
     *
     * @param int $channel
     * @return OrderCalcService
     * @throws OrderException
     */
    public function preCheckGiftProductStock(int $channel): OrderCalcService
    {
        $orderProducts = collect($this->orderPosDetail ?? []);
        if ($giftProducts = $orderProducts->where("freeNum", ">", 0)) {
            // todo 获取门店当前赠菜活动以及赠菜库存信息
            $giftActivity = ActivityService::getShopGiftActivityDetail(
                Shop::query()->where("id", $this->shopId)->first(), $channel
            );

            if ($giftActivity) {
                collect($giftActivity["giftProductIds"])
                    ->whereIn("productId", $giftProducts->pluck("productId")->toArray())
                    ->each(function ($configGiftProduct) use ($giftProducts) {
                        if ($configGiftProduct["giveStockRuleType"] == ActivityProductMappingModel::GIVE_STOCK_RULE_TYPE_1) {
                            $orderGiftProduct = $giftProducts->where("productId", $configGiftProduct["productId"])
                                                             ->first();
                            if ($orderGiftProduct["freeNum"] > $configGiftProduct["stockNum"]) {
                                $productName = $this->getProductField($configGiftProduct['productId'], 'name');
                                throw new OrderException(
                                    OrderCodeEnum::ERR_ORDER_NO_ENOUGH_GIFT_PRODUCT,
                                    "赠品 【 {$productName} 】 库存不足"
                                );
                            }
                        }
                    });
            }
        }
        return $this;
    }

    /**
     * 初始化 整单优惠
     *
     * @param $discount
     * @return $this
     */
    public function initOrderDiscount(array $discount): OrderCalcService
    {
        $this->setOrderPosDiscount($discount);
        return $this;
    }

    /**
     * 初始化 订单来源
     *
     * @param $sourceType
     * @return OrderCalcService
     */
    public function initOrderSourceType(int $sourceType): OrderCalcService
    {
        $this->setSourceType($sourceType);
        return $this;
    }

    /**
     * 设置订单 订单 优惠券
     *
     * @param array $coupon
     * @param int $couponSource
     * @return $this
     * @throws OrderException
     */
    public function initOrderUserCouponV3(
        array $coupon = [],
        int $couponSource = OrderCouponSplitModel::COUPON_SOURCE_APPLET
    ): OrderCalcService {
        if (!empty($coupon)) {

            // TODO 判断是否有预先查询过

            // ToDo 获取当前使用优惠券对应的数据
            $couponCodeList = array_column($coupon, 'couponCode');

            $productsCollection = collect($this->getProductLists());
            $couponList         = CustomerCouponModel::query()
                                                     ->with("productRel", function ($query) {
                                                         $query->where("menuId",
                                                             ShopService::getShopInfo($this->shopId)->menuId)
                                                               ->select(["id", "menuId", "imageUrl"]);
                                                     })
                                                     ->where('userId', $this->userId)
                                                     ->where("startAt", "<=", time())
                                                     ->where("endAt", ">=", time())
                                                     ->whereIn('couponCode', $couponCodeList)
                                                     ->get()
                                                     ->each(function ($item) use ($couponSource, $productsCollection) {
                                                         $item['customerCouponId'] = $item['id'];
                                                         $item['couponSource']     = $couponSource;
                                                         //todo  增加逻辑 使用菜品价格覆盖当前商品券折扣金额
                                                         if (
                                                             $item->couponType == CouponEnum::COUPON_TYPE_EXCHANGE
                                                             &&
                                                             $product = $productsCollection->where("productId",
                                                                 $item->canUseProduct)->first()
                                                         ) {
                                                             $item['discount'] = $product["price"];
                                                         }

                                                         //todo 菜品图片
                                                         if (
                                                             $item->couponType == \App\Kuafu\CustomerCoupon\Enum\CodeEnum::COUPON_TYPE_EXCHANGE
                                                             &&
                                                             $item->productRel
                                                         ) {
                                                             $item->productRel->imageUrl = picturePath($item->productRel->imageUrl);
                                                         }
                                                     })->toArray();

            $this->setOrderUserCoupon($couponList, true);
        }

        return $this;
    }


    /**
     * 设置 订单 用户 优惠券
     *
     * @param array $data
     * @return $this
     * @throws OrderException
     */
    public function initOrderPosUserCoupon(
        array $data
    ): OrderCalcService {
        $coupon = $data['couponList'];
        if (!empty($coupon)) {
            // 判断是否多种券相互叠加
            if (
                count($coupon) > 2 || // 多张商品券
                count($data['extCoupon']) != 0 || // 含有三方券
                count($data['posCouponList']) > 1 // 店内活动券
            ) {
                throw new OrderException(OrderCodeEnum::ERR_ORDER_DB_ADD, '暂不支持多券混合使用。');
            }


            // todo  获取优惠券对应的数据
            $couponCodeList = array_column($coupon, 'couponCode');
            $couponList     = CustomerCouponModel::query()
                                                 ->with("couponShopRel")
                                                 ->whereIn('couponCode', $couponCodeList)
                                                 ->get();

            // todo 校验优惠券有效期
            $couponList->each(function (CustomerCouponModel $customerCouponModel) {
                if ($customerCouponModel->startAt > time()) {
                    throw new OrderException(OrderCodeEnum::ERR_ORDER_DB_ADD,
                        "优惠券 【{$customerCouponModel->name}】 未生效");
                }

                if ($customerCouponModel->endAt < time()) {
                    throw new OrderException(OrderCodeEnum::ERR_ORDER_DB_ADD,
                        "优惠券 【{$customerCouponModel->name}】 已过期");
                }
            });

            //todo 用户优惠券
            $exchangeCouponCount    = $couponList->where("couponType", CouponEnum::COUPON_TYPE_EXCHANGE)->count();
            $nonExchangeCouponCount = $couponList->where("couponType", "<>", CouponEnum::COUPON_TYPE_EXCHANGE)->count();

            // todo pos 优惠券 & 用户非商品券和pos非商品券活动不可共享判断
            $posCouponCollection       = collect($this->getOrderPosCoupon() ?? []);
            $posNonExchangeCouponCount = $posCouponCollection->where(
                "couponType", "<>", CouponEnum::COUPON_TYPE_EXCHANGE
            )->count();
            if ($posNonExchangeCouponCount > 0 && $nonExchangeCouponCount > 0) {
                throw new OrderException(OrderCodeEnum::ERR_ORDER_DB_ADD, '用户非商品券和pos非商品券活动不可共享');
            }

            // todo 整单优惠 & 用户券不可同享校验
            if (attributeGet($this->orderPosDiscount, "discountNum", 0) > 0 && $nonExchangeCouponCount > 0) {
                throw new OrderException(OrderCodeEnum::ERR_ORDER_DB_ADD, '整单优惠和用户折扣券或代金券不可同享');
            }

            // todo 判断用户券最多 商品券+非商品券 最多一样一张
            if ($exchangeCouponCount > 1 || $nonExchangeCouponCount > 1) {
                throw new OrderException(OrderCodeEnum::ERR_ORDER_DB_ADD, '用户优惠券暂不支持非商品券混合使用');
            }


            // todo 获取门店信息
            $shopModel = ShopService::getShopInfo($this->shopId);

            // todo 校验券使用门店范围
            $couponList->each(function (CustomerCouponModel $customerCouponModel) use ($shopModel) {
                switch ($customerCouponModel->shopType) {
                    // todo 加盟店&直营店
                    case \App\Kuafu\CustomerCoupon\Enum\CodeEnum::COUPON_SHOP_TYPE_JOIN:
                    case \App\Kuafu\CustomerCoupon\Enum\CodeEnum::COUPON_SHOP_TYPE_DIRECTLY:
                        if ($shopModel->type != $customerCouponModel->shopType) {
                            throw new OrderException(OrderCodeEnum::ERR_ORDER_DB_ADD,
                                "当前 [{$customerCouponModel->name}] 券暂不支持此门店使用!");
                        }
                        break;
                    // todo 部分门店
                    case \App\Kuafu\CustomerCoupon\Enum\CodeEnum::COUPON_SHOP_TYPE_PART:
                        $shopIds = array_column(
                            attributeGet($customerCouponModel, "couponShopRel", collect([]))->toArray(),
                            "shopId"
                        );
                        if (!in_array($this->shopId, $shopIds)) {
                            throw new OrderException(OrderCodeEnum::ERR_ORDER_DB_ADD,
                                "当前 [{$customerCouponModel->name}] 券暂不支持此门店使用!");
                        }
                        break;
                    default:
                        break;
                }
            });

            // 判断是否有 orderId
            if (empty($this->orderId)) {
                // 筛选 未使用的
                $couponUnusedList = $couponList->where('status', 0);

                if ($couponList->count() != $couponUnusedList->count()) {
                    throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR, '优惠券状态非可用');
                }

                $couponList = $couponList
                    ->each(function ($item) {
                        $item['customerCouponId'] = $item['id'];
                        $item['couponSource']     = OrderCouponSplitModel::COUPON_SOURCE_APPLET;
                    })->toArray();


            } else {

                // 存在 orderId
                $couponList = $couponList
                    ->each(function ($item) {
                        $item['customerCouponId'] = $item['id'];
                        $item['couponSource']     = OrderCouponSplitModel::COUPON_SOURCE_APPLET;

                        // 调整券状态
                        if ($item['status'] == \App\Kuafu\CustomerCoupon\Enum\CodeEnum::COUPON_STATUS_LOCK) {
                            $item['status'] = OrderCouponSplitModel::STATUS_WAIT;
                        }

                        if ($item['status'] == \App\Kuafu\CustomerCoupon\Enum\CodeEnum::COUPON_STATUS_USED) {
                            $item['status'] = OrderCouponSplitModel::STATUS_USED;
                        }
                    })->toArray();
            }

            $this->setOrderUserCoupon($couponList, true);
        }

        return $this;
    }

    /**
     * pos 预计算核销用户券使用门槛
     *
     * @throws OrderException
     */
    public function posPreCalculateThreshold()
    {
        // pos用户优惠券
        $userCouponCollection = collect($this->getOrderUserCoupon() ?? []);
        // pos活动优惠
        $posCouponCollection = collect($this->getOrderPosCoupon() ?? []);


        // todo 获取用户优惠券 - 商品券信息
        $userExchangeCoupon = $userCouponCollection->where(
            "couponType", CouponEnum::COUPON_TYPE_EXCHANGE
        )->first();

        //todo 用户商品券可用商品信息
        if ($userExchangeCoupon) {
            $userExchangeCouponUseProducts = array_filter(explode(",", $userExchangeCoupon["canUseProduct"]));
        } else {
            $userExchangeCouponUseProducts = [];
        }

        //todo 剔除 (赠菜 & 反结账菜品 & 商品券可使用商品) 后商品数据
        $effectiveProductsCollection = collect($this->getProductLists())->map(
            function (array $product) use ($userExchangeCouponUseProducts) {
                $freeNum           = $product["freeNum"] ?? 0;
                $refundNum         = $product["refundNum"] ?? 0;
                $userExchangeNum   = in_array($product["productId"], $userExchangeCouponUseProducts) ? 1 : 0;
                $product["number"] = $product["number"] - $freeNum - $refundNum;
                if ($product["number"] < 0) {
                    throw new OrderException(
                        OrderCodeEnum::ERR_ORDER_ADD_ERROR,
                        "订单提交数据有误"
                    );
                }
                $product["number"] = $product["number"] - $userExchangeNum;
                if ($product["number"] < 0) {
                    $product        = collect($this->getOrderProductDetail())
                        ->values()
                        ->where("id", $userExchangeCouponUseProducts[0])
                        ->first();
                    $useProductId   = attributeGet($product, "id", "-");
                    $useProductName = attributeGet($product, "name", "-");
                    throw new OrderException(
                        OrderCodeEnum::ERR_ORDER_ADD_ERROR,
                        "当前订单总金额未满足优惠券使用门槛,请加购 【{$useProductId} - {$useProductName}】 菜品后 再次发起支付"
                    );
                }

                return $product;
            }
        )->values();

        //todo pos活动优惠存在情况下 此时不考虑商品券（暂未放开）
        if ($posCouponCollection->isNotEmpty()) {
            // todo  此时pos活动券有且仅有一张
            $posCoupon = $posCouponCollection->getIterator()->current();
            // todo pos活动券可使用菜品id
            $posCouponSuitProductIds = array_filter(
                explode(",", attributeGet($posCoupon, "canUseProduct", ""))
            );
            // todo 此时有效商品总价
            $effectiveProductsPrice = $this->calculateSelectedProductsPrice(
                $effectiveProductsCollection->toArray(),
                $posCouponSuitProductIds
            );
            $posCouponUseCondition  = attributeGet($posCouponCollection->getIterator()->current(), "useCondition", 0);
            $residualThreshold      = bcsub($posCouponUseCondition, $effectiveProductsPrice);
            $canUse                 = $effectiveProductsPrice > 0 && $residualThreshold <= 0;
            if (!$canUse) {
                $residualThresholdRmb = bcdiv100($residualThreshold);
                throw new OrderException(
                    OrderCodeEnum::ERR_ORDER_ADD_ERROR,
                    "当前订单总金额未满足pos活动优惠券使用门槛,请加购 【{$residualThresholdRmb}】 元后 再次发起支付"
                );
            }
        }


        // todo 用户券存在情况下
        if ($userCouponCollection->isNotEmpty()) {
            // todo 非商品券信息
            $userNonExchangeCoupon = $userCouponCollection->where(
                "couponType", "<>", CouponEnum::COUPON_TYPE_EXCHANGE
            )->first();

//            // todo 判断剔除赠菜后 商品券是否还可用
//            if($userExchangeCoupon){
//                $existsProduct     = $effectiveProductsCollection->whereIn("productId", $userExchangeCouponUseProducts)
//                                                        ->where("number", ">", 0)
//                                                        ->count();
//
//                if (!$existsProduct) {
//                    $product        = collect($this->getOrderProductDetail())
//                        ->values()
//                        ->where("id", $userExchangeCouponUseProducts[0])
//                        ->first();
//                    $useProductId   = attributeGet($product, "id", "-");
//                    $useProductName = attributeGet($product, "name", "-");
//                    throw new OrderException(
//                        OrderCodeEnum::ERR_ORDER_ADD_ERROR,
//                        "当前订单总金额未满足优惠券使用门槛,请加购 【{$useProductId} - {$useProductName}】 菜品后 再次发起支付"
//                    );
//                }
//            }


            switch ($userCouponCollection->count()) {
                case 1:// todo 使用一张券时 门槛校验
                    // todo 非商品券门槛校验
                    if ($userNonExchangeCoupon) {
                        // todo 券可使用商品
                        $couponUseProducts = array_filter(explode(",", $userNonExchangeCoupon["canUseProduct"]));
                        // todo 券可使用商品总价
                        $couponUseProductsPrice = $this->calculateSelectedProductsPrice(
                            $effectiveProductsCollection->toArray(),
                            $couponUseProducts
                        );
                        // todo  券可使用商品总价大于0 && （无门槛 or  券可使用商品总价大于门槛）
                        $residualThreshold = bcsub($userNonExchangeCoupon["useCondition"], $couponUseProductsPrice);
                        $condition         = $couponUseProductsPrice > 0 && $residualThreshold <= 0;
                        if (!$condition) {
                            $residualThresholdRmb = bcdiv100($residualThreshold);
                            throw new OrderException(
                                OrderCodeEnum::ERR_ORDER_ADD_ERROR,
                                "当前订单总金额未满足优惠券使用门槛,请加购 {$residualThresholdRmb}元后 再次核销"
                            );
                        }
                    }
                    break;
                case 2: // todo 商品券&非商品券组合使用 校验非商品券门槛
                    // todo 非商品券可使用商品
                    $nonExchangeCouponUseProducts = array_filter(explode(",", $userNonExchangeCoupon["canUseProduct"]));
                    // todo 计算非商品券可使用剔除商品券后的商品总价
                    $nonExchangeCouponUseProductsPrice = $this->calculateSelectedProductsPrice(
                        $effectiveProductsCollection->toArray(),
                        $nonExchangeCouponUseProducts
                    );

                    // todo 剩余门槛计算
                    $residualThreshold = bcsub(
                        $userNonExchangeCoupon["useCondition"], $nonExchangeCouponUseProductsPrice
                    );
                    $canUse            = $nonExchangeCouponUseProductsPrice > 0 && $residualThreshold <= 0;

                    if (!$canUse) {
                        $residualThresholdRmb = bcdiv100($residualThreshold);
                        throw new OrderException(
                            OrderCodeEnum::ERR_ORDER_ADD_ERROR,
                            "当前订单菜品券使用后，折扣券将不满足使用门槛！请加购 {$residualThresholdRmb}元后 再次核销"
                        );
                    }
                    break;
            }
        }
    }

    /**
     * 计算传入商品中选中商品的总价 不传选中商品则返回总价
     *
     * @param array $products 所有商品
     * @param array $selectedProductIds 选中商品
     * @return float|int
     */
    public function calculateSelectedProductsPrice(array $products, array $selectedProductIds = [])
    {
        $totalPrice = 0;
        collect($products)->when($selectedProductIds, function ($query) use ($selectedProductIds) {
            return $query->whereIn("productId", $selectedProductIds);
        })->each(function ($product) use (&$totalPrice) {
            $totalPrice += (($product["number"]) * $product["price"]);
        });
        return $totalPrice;
    }

    /**
     * 设置订单 匿名 优惠券[非用户]
     *
     * @param array $coupon
     * @param int $couponSource
     * @return $this
     * @throws OrderException
     */
    public function initOrderPosCoupon(
        array $coupon = [],
        int $couponSource = OrderCouponSplitModel::COUPON_SOURCE_POS
    ): OrderCalcService {
        if (!empty($coupon)) {

            // 如果是小程序禁止用 新券
            if ($this->sourceType == CodeEnum::SOURCE_TYPE_MINI && !empty($this->orderId)) {
                foreach ($coupon as $c) {
                    if (empty($c['id'])) {
                        throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR, '小程序订单禁止使用优惠券。');
                    }
                    $couponSource = OrderCouponSplitModel::COUPON_SOURCE_APPLET;
                }
            }

            $new = [];
            $old = [];
            foreach ($coupon as $c) {
                if (empty($c['id'])) {
                    $new[] = $c;
                } else {
                    $old[] = $c;
                }
            }

            $hasList = [];
            if (!empty($old)) {
                // 先获取最新的券信息
                $hasUsedId = array_column($old, 'id');

                // 库里查询用过的券 信息
                $hasList = OrderCouponSplitModel::suffix($this->shopId)->query()->whereIn('id', $hasUsedId)->get();
                $count   = $hasList->count();
                // 判断是否查询的正常
                if (count($hasUsedId) != $count) {
                    throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR, '用券信息异常, 请联系门店运营人员.');
                }

                $hasList = $hasList->toArray();
            }

            $couponIdList = array_column($coupon, 'couponId');
            $couponList   = CouponModel::query()->whereIn('id', $couponIdList)
                                       ->get()
                                       ->each(function ($item) use ($couponSource, &$hasList) {
                                           // 优惠券实际优惠
                                           $item['couponDiscountPrice'] = 0;
                                           // 修正后续字段
                                           $item['couponType']       = $item['type'];
                                           $item['couponId']         = $item['id'];
                                           $item['customerCouponId'] = 0;
                                           $item['couponSource']     = $couponSource;
                                           $item['isNew']            = true;
                                           unset($item['type']);
                                       })
                                       ->toArray();


            //todo 判断整单优惠和店内活动(折扣券|代金券)同享校验
            if (
                attributeGet($this->orderPosDiscount, "discountNum", 0) > 0
                &&
                collect($couponList)
                    ->where("couponType", "<>", \App\Kuafu\CustomerCoupon\Enum\CodeEnum::COUPON_TYPE_EXCHANGE)
                    ->count() > 0
            ) {
                throw new OrderException(OrderCodeEnum::ERR_ORDER_DB_ADD, '整单优惠和店内折扣券或代金券活动不可同享');
            }

            // 处理优惠券张数
            $couponListById = array_column($couponList, null, 'id');

            foreach ($coupon as $c) {
                for ($i = 0; $i < $c['num'] ?? 1; $i++) {
                    $couponInfo = $couponListById[$c['couponId']];
                    foreach ($hasList as $key => $l) {
                        if ($l['couponId'] == $c['couponId']) {
                            $couponInfo['customerCouponId'] = $l['customerCouponId'];
                            unset($hasList[$key]);
                        }
                    }
                    $this->setOrderPosCoupon($couponInfo ?? []);
                }
            }
        }
        return $this;
    }

    /**
     * 初始化 订单 支付方式
     *
     * @param array $data
     * @return $this
     * @throws OrderException
     */
    public function initOrderPayment($data): OrderCalcService
    {
        if (!empty($this->orderId)) {
            PayStreamModel::getOrderPayStreamListById($this->orderId)->each(function ($item) {
                $orderPayment = [
                    'id'               => $item->payNo,
                    'payChannel'       => $item->payChannel,
                    'reckonType'       => $item->isPaidIn == PayStreamModel::PAID_IN_YES ?
                        CustomPaymentEnum::RECKON_TYPE_INCOME : CustomPaymentEnum::RECKON_TYPE_DISCOUNT,
                    'payMoney'         => $item->validPayPrice,// 有效金额 刨去 退款金额
                    'isNew'            => false,
                    'orderPaymentType' => $item->payChannel > PayStreamModel::PAY_CHANNEL_CUSTOM_PAYMENT
                        ? CodeEnum::ORDER_PAYMENT_TYPE_CUSTOM_PAYMENT : CodeEnum::ORDER_PAYMENT_TYPE_FIAT
                ];

                $this->setOrderPaymentList($orderPayment);
            });
        }


        $paymentList = $data['payMethodList'];

        if (!empty($paymentList)) {
            // 如果是小程序禁止用 自定义支付
            if ($this->sourceType == CodeEnum::SOURCE_TYPE_MINI && !empty($this->orderId)) {
                foreach ($paymentList as $p) {
                    if (empty($p['id'])) {
                        throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR, '小程序订单禁止使用自定义支付。');
                    }
                }
            }

            // 校验自定义支付方式
            $customPaymentList    = CustomPaymentService::enableCustomPaymentLists($this->shopId)['customPaymentLists'];
            $customPaymentListArr = array_column($customPaymentList, null, 'payId');

            foreach ($paymentList as $payment) {

                if (!empty($payment['id'])) {
                    continue;
                }

                if (!array_key_exists($payment['paymentId'], $customPaymentListArr)) {
                    throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR, '自定义支付方式不存在');
                }
                $paymentInfo = $customPaymentListArr[$payment['paymentId']];

                $orderPayment = [
                    'id'               => '',
                    'payChannel'       => $payment['paymentId'],
                    'reckonType'       => (int)$paymentInfo['reckonType'],
                    'payMoney'         => (int)$payment['payMoney'],
                    'isNew'            => true,
                    'orderPaymentType' => $payment['paymentId'] > PayStreamModel::PAY_CHANNEL_CUSTOM_PAYMENT
                        ? CodeEnum::ORDER_PAYMENT_TYPE_CUSTOM_PAYMENT : CodeEnum::ORDER_PAYMENT_TYPE_FIAT
                ];

                $this->setOrderPaymentList($orderPayment);
            }
        }

        // 判断是否试吃
        // 试吃结算->转成自定义支付
        $paymentChannel = $data['payMethod'];
        if ($paymentChannel == CodeEnum::ORDER_PAY_CHANNEL_TRY_TACK) {

            // 校验是否存在这种支付方式
            $customPaymentList = CustomPaymentService::enableCustomPaymentLists($this->shopId)['customPaymentLists'];

            $customPaymentListArr = array_column($customPaymentList, null, 'payId');
            $paymentInfo          = $customPaymentListArr[CodeEnum::ORDER_PAY_CHANNEL_TRY_TACK];

            $orderPayment = [
                'id'               => '',
                'payChannel'       => CodeEnum::ORDER_PAY_CHANNEL_TRY_TACK,
                'reckonType'       => (int)$paymentInfo['reckonType'],
                'payMoney'         => -1, // -1 为 剩余全部金额
                'isNew'            => true,
                'orderPaymentType' => CodeEnum::ORDER_PAYMENT_TYPE_CUSTOM_PAYMENT
            ];
            $this->setOrderPaymentList($orderPayment);
        }

        return $this;
    }

    /**
     * 初始化 退款支付方式
     *
     * @param array $data
     * @param int $orderEvent
     * @return $this
     */
    public function initOrderRefundPayment(
        array $data,
        int $orderEvent = CodeEnum::ORDER_PAY_EVENT_ORDER
    ): OrderCalcService {

        $paymentList   = $this->getOrderPaymentList();
        $nullIdPayment = [];

        foreach ($paymentList as $key => $value) {
            if (empty($value['id'])) {
                $nullIdPayment[] = $value;
                unset($paymentList[$key]);
            }
        }

        $paymentList = array_column($paymentList, null, 'id');

        foreach ($data as $d) {
            $info = $this->getOrderPaymentInfo($d['id']);
            $this->setOrderRefundPaymentList($info);

            foreach ($paymentList as $key => &$payment) {
                if ($key == $d['id'] && $payment['payChannel'] == $d['paymentId']) {
//                    if ($payment['payMoney'] < $d['payMoney']) {
//                        // TODO 异常报错
//                    }

                    $payment['payMoney'] -= $d['payMoney'];
                    if ($payment['payMoney'] == 0) {
                        unset($paymentList[$key]);
                    }
                }
            }
        }


        if ($orderEvent == CodeEnum::ORDER_PAY_EVENT_ORDER) {

            $paymentList = array_merge($nullIdPayment, $paymentList);
            $paymentList = array_values($paymentList);

            $this->setOrderPaymentList($paymentList, true);
        }
        // 重新复制

        return $this;
    }

    /**
     * 初始化 退款 券
     *
     * @param array $data
     * @return $this
     */
    public function initOrderRefundPosCouponList(array $data): OrderCalcService
    {
        if (!empty($data)) {
            $ids = [];
            foreach ($data as $datum) {
                $ids[] = (int)$datum['id'];
            }

            $couponList = OrderCouponSplitModel::suffix($this->shopId)->getOrderCouponsById($ids);

            if (!empty($couponList)) {
                $this->setOrderRefundPosCouponList($couponList->toArray(), true);
            }
        }

        return $this;
    }

    /**
     * 初始化 券包 ID
     *
     * @param int $couponPackageId
     * @return $this
     * @throws OrderException
     */
    public function initCouponPackageId(int $couponPackageId): OrderCalcService
    {
        if (!empty($couponPackageId)) {
            // 校验券包 ID 是否合法
            // TODO 校验券包是否符合购买条件
            $couponPackageInfo = CouponPackageModel::query()->where('id', $couponPackageId)->first();
            if (!empty($couponPackageInfo)) {

                // todo 判断是否存在已购买券包记录 || 已下单未付款记录
                $buyCount = CouponPackageOrderModel::query()
                                                   ->where("userId", $this->userId)
                                                   ->where("couponPackageId", $couponPackageId)
                                                   ->where("payStatus", CouponPackageOrderModel::PAY_STATUS_1)
                                                   ->count();

                if ($buyCount) {
                    throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR, '您已经购买过此券包，无需再次购买。');
                }

                $existsOrder = CouponPackageOrderModel::query()
                                                      ->where("userId", $this->userId)
                                                      ->where("couponPackageId", $couponPackageId)
                                                      ->where(function ($query) {
                                                          $query->where(function ($query) {
                                                              $query->where(
                                                                  "payStatus", CouponPackageOrderModel::PAY_STATUS_0
                                                              )->where(
                                                                  "createdAt", ">=",
                                                                  Carbon::now()->subMinutes(15)->timestamp
                                                              );
                                                          });
                                                      })->count();

                if ($existsOrder) {
                    throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR,
                        '您有加购该券包的待支付订单，请勿重复购买。');
                }


                $this->setCouponPackageInfo($couponPackageInfo->toArray());
                $this->setCouponPackageId($couponPackageId);

//                // 获取券包对应的券列表 ID
//                $couponPackageCouponList = CouponPackageMappingModel::query()
//                                                                    ->where('couponPackageId', $couponPackageId)
//                                                                    ->get();

//                $cListIds = $couponPackageCouponList->pluck('couponId');

                // 获取 券包 信息
//                $couponList = CouponModel::query()->whereIn('id', $cListIds)->get()->toArray();

                // 筛选可用券信息
                $productList = [];
                foreach ($this->orderPosDetail as $posDetail) {
                    $productList[] = [
                        'productId' => $posDetail['productId'],
                        'number'    => $posDetail['num'],
                        'price'     => $this->getProductField($posDetail['productId'], 'price')
                    ];
                }

                $result = CouponPackageService::getEnableCouponList($this->brandId, $this->shopId, $productList,
                    $this->sourceType, $couponPackageId);

                $result = OrderService::addCouponSourceType($result,
                    OrderCouponSplitModel::COUPON_SOURCE_COUPON_PACKAGE);

                $this->setCouponPackageCouponList($result['enable']);
                $this->setCouponUnablePackageCouponList($result['disable']);
            }
        }

        return $this;
    }

    /**
     * 勾选的匿名券列表 [券包里的券信息]
     *
     * @param array $coupon
     * @return $this
     * @throws OrderException
     */
    public function initAnonymousCouponList(array $coupon = []): OrderCalcService
    {
        // 判断勾选的券 是否在券包里
        if (!empty($coupon)) {
            $ids = array_column($this->getCouponPackageCouponList(), 'id');

            $cIds = array_column($coupon, 'couponId');

            if ($cIds !== array_intersect($cIds, $ids)) {
                throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR, '券包选择的券有误');
            }

            $coupon = array_map(function ($arr) {
                $arr['num'] = 1;
                return $arr;
            }, $coupon);

            if (!empty($this->getOrderUserCoupon())) {
                throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR, '用户的券不能和新购券包中券同时 使用.');
            }

            $this->initOrderPosCoupon($coupon, OrderCouponSplitModel::COUPON_SOURCE_COUPON_PACKAGE);
        }

        return $this;
    }

    /**
     * 设置订单 订单 三方优惠券
     *
     * @param array $coupon
     * @return $this
     */
    public function initOrderCouponExt(array $coupon = []): OrderCalcService
    {
        $this->orderPosCouponExt = $coupon ?? [];
        return $this;
    }

    /**
     * 设置订单 订单退回的 三方优惠券
     *
     * @param array $coupon
     * @return $this
     */
    public function initOrderCouponExtRefund(array $coupon = []): OrderCalcService
    {
        $this->orderPosCouponExtRefund = $coupon ?? [];
        return $this;
    }

    /**
     * 初始化用户 Id
     *
     * @param $userId
     * @return $this
     */
    public function initUserId($userId): OrderCalcService
    {
        $this->userId = $userId;
        return $this;
    }


    /**
     * 校验自配送状态
     *
     * @param array $data
     * @return $this
     * @throws OrderException
     */
    public function initSelfDelivery(array $data): OrderCalcService
    {
        $this->isSelfDelivery = $data['isSelfDelivery'] ?? 0;

        if ($this->isSelfDelivery) {
            // 校验地址是否准确
            if (empty($data['deliveryAddress']['detailInfo'])) {
                throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR, '自配送地址不能为空');
            }

            // 校验联系人和电话是否准确
            if (empty($data['deliveryAddress']['telNumber']) || empty($data['deliveryAddress']['userName'])) {
                throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR, '自配送联系人和电话不能为空');
            }
        }

        return $this;
    }

    /**
     * 校验配送类型
     *
     * @param array $data
     * @return $this
     * @throws OrderException
     */
    public function initDeliveryType(array $data): OrderCalcService
    {
        $deliveryType = $data['deliveryType'] ?? 0;
        switch ($deliveryType) {
            case CodeEnum::DELIVERY_TYPE_DISTRIBUTION: // 渠道配送
                // 校验地址是否准确
                if (empty($data['deliveryAddress']['detailInfo'])) {
                    throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR, '渠道配送地址不能为空');
                }

                // 渠道 id
                if (empty($data['deliveryAddress']['distributionId'])) {
                    throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR, '订单二维码已过期, 请重新扫码下单');
                }

                break;
            case CodeEnum::DELIVERY_TYPE_DISTRIBUTION_SELF: // 渠道自提
                // 渠道 id
                if (empty($data['deliveryAddress']['distributionId'])) {
                    throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR, '订单二维码已过期, 请重新扫码下单');
                }
                break;
            case CodeEnum::DELIVERY_TYPE_DEFAULT:
            case CodeEnum::DELIVERY_TYPE_SELF_DELIVERY: // 自配送, 校验走原先逻辑
            default:
                break;
        }
        $this->deliveryType = $deliveryType;

        return $this;
    }

    /**
     * 初始化 POS 传过来的订单总金额
     *
     * @param $totalPrice
     * @return $this
     */
    public function initOrderPosTotalPrice($totalPrice): OrderCalcService
    {
        $this->orderPosTotal = $totalPrice ?? [];
        return $this;
    }

    /**
     * 校验商品菜品数据信息
     *
     * @throws OrderException
     */
    public function checkOrderDetailV3(): OrderCalcService
    {
        if (count($this->orderProductDetail) == 0) {
            throw new OrderException(OrderCodeEnum::ERR_ORDER_DISH_MISS,
                '菜品缺失, 请更新菜单后再试, 若还有问题请联系门店运营人员');
        }

        // 获取商品估清/下架数据
        $productStatus = ShopService::sGetStatusList($this->shopId, $this->productIds);

        if (!$productStatus->isEmpty()) {
            // 存在估清商品
            $downProductIds = $productStatus->pluck('productId');

            $errorMsg = '';
            foreach ($downProductIds as $v) {
                $errorMsg = $this->orderProductDetail[$v]['name'] . ' ';
            }

            $errorMsg .= '商品状态为下架或者估清状态,请重新下单.';
            throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR, $errorMsg);
        }

        // TODO #1 校验传过来的 折扣以及减免是否符合基本规则

        return $this;
    }

    /**
     * 校验状态
     *
     * @return $this
     * @throws OrderException
     */
    public function checkOrderCouponStatus()
    {
        foreach ($this->getOrderUserCoupon() as $coupon) {
            if (($coupon['status'] ?? -1) != \App\Kuafu\CustomerCoupon\Enum\CodeEnum::COUPON_STATUS_NO_USED) {
                throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR, '优惠券已使用或正在使用中');
            }
        }

        return $this;
    }

    /**
     * 校验状态
     *
     * @throws OrderException
     */
    public function checkOrderPosCoupon()
    {
        // 格式化 传入的优惠券列表
        // POS 不校验小程序券
        if ($this->sourceType == CodeEnum::SOURCE_TYPE_MINI) {
            return;
        }
        $posCouponList = [];
        foreach ($this->getOrderPosCoupon() as $posCoupon) {
            $posCouponList[] = [
                'couponId' => $posCoupon['couponId'] ?? 0,
            ];
        }

        if (!empty($posCouponList)) {
            $productList = [];
            foreach ($this->orderPosDetail as $posDetail) {
                $productList[] = [
                    'productId' => $posDetail['productId'],
                    'number'    => $posDetail['num'],
                    'price'     => $this->getProductField($posDetail['productId'], 'price')
                ];
            }
            CouponService::getPosEnableCouponList($this->brandId, $this->shopId, $productList, $posCouponList,
                $this->sourceType);
        }

    }

    /**
     * 校验 POS 和 美团 优惠券只能用一侧
     *
     * @throws OrderException
     */
    public function checkOrderPosUseOneCoupon()
    {
        // POS 传入的 优惠券列表
        $posCouponList = $this->getOrderPosCoupon();

        // POS 传入的 美团优惠券列表
        $meiTuanDCouponList = $this->getOrderPosCouponExt();

        if (count($posCouponList) > 0 && count($meiTuanDCouponList) > 0) {
            throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR, '美团/抖音 优惠券和店内活动券不能同享');
        }
    }

    /**
     * 校验商品菜品数据信息
     *
     * @throws OrderException
     */
    public function checkPosPrice()
    {
        // 本地跳过校验
        if (config('app.env') == 'dev') {
            return $this;
        }
        // 判断是否与 POS 传来的最终金额是否一致
        if (0 !== bccomp($this->orderPosTotal['originalPrice'], $this->orderOriginalPrice)) {

            $msg = '';

            if (config('app.env') != 'prod') {
                $msg = "订单原价POS{$this->orderPosTotal['originalPrice']}, RMS{$this->orderOriginalPrice}校验有误, ";
            }

            $msg .= '金额计算有误, 请更新菜单后再试, 若还有问题请联系门店运营人员.';

            throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR, $msg);
        }

        // 美团金额跳过校验, 内部优惠券金额跳过校验
//        if ("0" == $this->extDiscountPrice || "0" == $this->couponPrice) {
//            if (0 !== bccomp(bcsub($this->orderPosTotal['shouldPrice'], $this->extDiscountPrice),
//                    $this->orderShouldPrice)) {
//                throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR,
//                    "订单应付金额 POS{$this->orderPosTotal['shouldPrice']}, RMS{$this->orderShouldPrice}校验有误");
//            }
//        }

        return $this;
    }

    /**
     * 更新反结账 detail 详情
     *
     * @param array $orgOrderDetail
     * @param array $posOrderDetail
     */
    public function updateOrderDetailReserve($orgOrderDetail = [], $posOrderDetail = [])
    {

        $orgOrderDetail = array_column($orgOrderDetail, null, 'id');

        $diffDetail = [];
        $addDetail  = [];
        $delDetail  = [];

        foreach ($posOrderDetail as $posDetail) {
            $posKey = $posDetail['id'];
            if ($posKey < 0) {
                $posDetail['orderId'] = $this->orderId;

//                $posDetail['createdAt'] = $this->createdAt;
//                $posDetail['updatedAt']    = $this->updatedAt;
                $posDetail['createdByUid'] = $this->getModifyUser()['id'] ?? 0;
                $posDetail['updatedByUid'] = $this->getModifyUser()['id'] ?? 0;
                $posDetail['createdBy']    = $this->getModifyUser()['username'] ?? '';
                $posDetail['updatedBy']    = $this->getModifyUser()['username'] ?? '';

                unset($posDetail['id']);
                $addDetail[] = $posDetail;
                continue;
            }

            if (!key_exists($posKey, $orgOrderDetail)) {
                continue;
            }
            $orgDetail           = $orgOrderDetail[$posKey];
            $diffDetail[$posKey] = [];

            // 更新数量
            if ($orgDetail['num'] != $posDetail['num']) {
                $diffDetail[$posKey] += [
                    'num' => $posDetail['num']
                ];
            }

            // 更新退款的时候菜品总价格
            if ($orgDetail['refundNum'] != $posDetail['refundNum']) {
                $diffDetail[$posKey] += [
                    'refundNum' => $posDetail['refundNum']
                ];
            }

            // 更新赠送
            if ($orgDetail['freeNum'] != $posDetail['freeNum']) {
                $diffDetail[$posKey] += [
                    'freeNum' => $posDetail['freeNum']
                ];
            }

            // 更新折扣
            if ($orgDetail['discountNum'] != $posDetail['discountNum']) {
                $diffDetail[$posKey] += [
                    'discountType'  => $posDetail['discountType'],
                    'discountNum'   => $posDetail['discountNum'],
                    'discountPrice' => $posDetail['discountPrice'],
                ];
            }

            // 更新应付
            if ($orgDetail['payPrice'] != $posDetail['payPrice']) {
                $diffDetail[$posKey] += [
                    'payPrice' => $posDetail['payPrice']
                ];
            }

            // 更新单价应付
            if ($orgDetail['shouldUnitPrice'] != $posDetail['shouldUnitPrice']) {
                $diffDetail[$posKey] += [
                    'payPrice' => $posDetail['shouldUnitPrice']
                ];
            }

            // 更新总原价
            if ($orgDetail['originalPrice'] != $posDetail['originalPrice']) {
                $diffDetail[$posKey] += [
                    'originalPrice' => $posDetail['originalPrice']
                ];
            }

        }

        $posOrderDetail = array_column($posOrderDetail, null, 'id');
        $posKeys        = array_keys($posOrderDetail);
        $orgKeys        = array_keys($orgOrderDetail);
        $delDetail      = array_diff($orgKeys, $posKeys);
        $diffDetail     = array_filter($diffDetail);
//        // 循环更新数据
//        foreach ($diffDetail as $key => $detail) {
////            OrderDetailModel::query()->where('id', $key)->update($detail);
//        }

        // 新增数据
//        OrderDetailModel::query()->insert($addDetail);

        // 删除订单详情
//        OrderDetailModel::query()->whereIn('id', $delDetail)->update(['isDeleted' => 1]);

    }

    private function calcSelfDelivery()
    {
        // 下面的代码为了 兼容以前的接口
        if ($this->isSelfDelivery == BaseModel::SWITCH_ON) {
            $deliveryType = CodeEnum::DELIVERY_TYPE_SELF_DELIVERY;
        } else {
            $deliveryType = $this->deliveryType ?? 0;
        }

        switch ($deliveryType) {
            case CodeEnum::DELIVERY_TYPE_SELF_DELIVERY: // 自配送, 校验走原先逻辑
                // 校验是否满足自配送金额
                $shopArchives = ShopArchivesService::getArchivesInfo($this->shopId);
                if (bccomp($this->orderOriginalPrice, $shopArchives['minSelfDeliveryPrice']) < 0) {
                    throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR,
                        '订单金额不满足自配送金额' . bcdiv100($shopArchives['minSelfDeliveryPrice']) . '元');
                }
                // 获取所有已点的菜品
                $arr            = collect($this->getOrderFilterOrderDetail())->pluck('productId');
                $allMustProduct = self::getAllMustProduct();
                if ($allMustProduct->diff($arr)->count() > 0) {
                    throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR, '必点商品未点, 暂不能下单支付');
                }
                break;
            case CodeEnum::DELIVERY_TYPE_DISTRIBUTION_SELF: // 渠道自提
                // 校验是否满足配送金额
                $distributionId   = (int)($this->postData['deliveryAddress']['distributionId'] ?? 0);
                $distributionInfo = ShopDistributionService::getEffectInfoById($distributionId);

                // 校验数据是否存在
                if (empty($distributionInfo)) {
                    throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR, '配送信息有误');
                }
                break;
            case CodeEnum::DELIVERY_TYPE_DISTRIBUTION: // 渠道配送
                // 校验是否满足配送金额
                $distributionId   = (int)($this->postData['deliveryAddress']['distributionId'] ?? 0);
                $distributionInfo = ShopDistributionService::getEffectInfoById($distributionId);

                // 校验数据是否存在
                if (empty($distributionInfo)) {
                    throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR, '配送信息有误');
                }

                // 校验起送金额
                if ($distributionInfo['minDeliveryAmount'] > 0 && bccomp($this->orderOriginalPrice,
                        $distributionInfo['minDeliveryAmount']) < 0) {
                    throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR,
                        '订单金额不满足渠道送金额' . bcdiv100($distributionInfo['minDeliveryAmount']) . '元');
                }

                break;

            case CodeEnum::DELIVERY_TYPE_DEFAULT:
            default:
                return;
        }


    }

    /**
     * 计算 单品价格 V4
     *
     * @throws OrderException
     */
    private function calcOne()
    {
        foreach ($this->orderPosDetail as $orderDetail) {
            // detail 的 id
            $detailId = $orderDetail['id'];
            // 赠菜数量
            $detailFreeNum = $orderDetail['freeNum'];
            // 退菜数量
            $detailRefundNum = $orderDetail['refundNum'];
            // 原始 菜品数量
            $orgDetailNum = $orderDetail['num'];
            // 菜品 ID
            $detailProductId = $orderDetail['productId'];
            // 单品 原单价
            $unitOriginPrice = $this->getProductField($detailProductId, 'price');
            // 单品 原总价格
            $detailOriginPrice = bcmul($unitOriginPrice, $orgDetailNum - $detailRefundNum);
            // 用于计算原价的数量
            $detailNum = bcsub($orgDetailNum, bcadd($detailFreeNum, $detailRefundNum));
            // 应付 总价
//            dd($unitOriginPrice, $detailOriginPrice);
            $shouldPrice = $detailOriginPrice;
            // 应付 单价
            $shouldUnitPrice = $unitOriginPrice;
            if ($detailNum == 0) {
                $shouldUnitPrice = 0;
            }
            // 折扣类型
            $discountType = $orderDetail['discountType'];
            // 折扣额度
            $discountNum = $discountType > 0 ? $orderDetail['discountNum'] : 0;
            // 单品打折/减免优惠的 金额
            $discountPrice = 0;
            // 单品总优惠（discountPrice + 整单优惠的平均 + 优惠券后的平均 + 赠菜的优惠金额）
            $totalDiscountPrice = 0;

//            // 退菜
//            if ($detailRefundNum > 0) {2
//                // TODO 暂时无操作
//            }

            // 赠菜
            if ($detailFreeNum > 0) {
                // 计算赠菜优惠
                $freeOriginPrice = bcmul($unitOriginPrice, $detailFreeNum);
                // 叠加到对应的数据上
                $totalDiscountPrice = $freeOriginPrice;
                // 菜品应付也去了
                $shouldPrice = bcsub($shouldPrice, $freeOriginPrice); // 应付总价;
            }

            // 折扣菜
            if ($discountType > 0) {
                switch ($discountType) {
                    case CodeEnum::PRODUCTION_DISCOUNT_TYPE_COUNT:
                        // 应付总价
                        $shouldPrice = OrderService::bcDiv(bcmul($discountNum, $detailOriginPrice));
                        // 应付单价
                        $shouldUnitPrice = OrderService::bcDiv(bcmul($discountNum, $unitOriginPrice));
                        break;
                    case CodeEnum::PRODUCTION_DISCOUNT_TYPE_ANNUL:
                        // 应付总价
                        $shouldPrice = bcsub($detailOriginPrice, $discountNum); // 应付总价;
                        // 应付单价
                        $shouldUnitPrice = bcdiv($shouldPrice, $detailNum); // 应付单价;
                        break;
                    default:
                        throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR, '折扣/减免类型异常');
                }

                // 优惠了的金额
                $discountPrice      = bcsub($detailOriginPrice, $shouldPrice);
                $totalDiscountPrice = $discountPrice;
            }

            // 订单 原价
            $this->orderOriginalPrice = bcadd($this->orderOriginalPrice, $detailOriginPrice);
            // 订单 优惠
            $this->orderDiscountPrice = bcadd($this->orderDiscountPrice, $totalDiscountPrice);
            // 订单 应付
            $this->orderShouldPrice = bcadd($this->orderShouldPrice, $shouldPrice);

            $detail = [
                'id'                  => $detailId,
                'productId'           => $detailProductId,
                'num'                 => $orgDetailNum,
                'originalUnitPrice'   => $unitOriginPrice,
                'originalPrice'       => $detailOriginPrice,
                'freeNum'             => $detailFreeNum,
                'shouldUnitPrice'     => $shouldUnitPrice,
                'payPrice'            => $shouldPrice,
                'discountType'        => $discountType,
                'discountNum'         => $discountNum,
                'discountPrice'       => $discountPrice,
                'totalDiscountPrice'  => $totalDiscountPrice,
                'orderDiscountPrice'  => 0,
                'couponDiscountPrice' => 0,
                'couponCostPrice'     => 0,
                'refundNum'           => $detailRefundNum,
                'type'                => $this->getProductField($detailProductId, 'type'),
                'packageList'         => $this->getProductField($detailProductId, 'packageList'),
                'remark'              => $orderDetail['remark'] ?? '',
                'menuId'              => $this->menuId,
            ];

            $this->setOrderFilterOrderDetail($detail);
        }
    }

    /**
     * 计算 套餐价格
     *
     */
    private function calcPackage()
    {
        $arr     = $this->getOrderFilterOrderDetail();
        $sonList = [];
        foreach ($arr as $detail) {
            if ($detail['type'] == ProductCode::PRODUCT_TYPE_PACKAGE) {
                $foreachNum = 0;
                // 套餐原总价价
                $detailOriginPrice = $detail['originalPrice'];
                // 套餐应付
                $detailPayPrice = $detail['payPrice'];
                // 套餐优惠金额
                $detailTotalDiscount = $detail['totalDiscountPrice'];
                // 套餐优惠券的收入
                $detailCouponCostPrice = $detail['couponCostPrice'];
                // 套餐数量
                $detailNum = $detail['num'];
                // 套餐退的数量
                $detailRefundNum = $detail['refundNum'];
                // 单品减免的金额
                $detailDiscountPrice = $detail['discountPrice'];
                // 整单减免的金额
                $detailOrderDiscountPrice = $detail['orderDiscountPrice'];
                // 优惠券优惠的金额
                $detailCouponDiscountPrice = $detail['couponDiscountPrice'];
                // 赠菜数量
                $detailFreeNum = $detail['freeNum'];
//                // 用户计算的数量
//                $calcNum = $detailNum - $detailRefundNum;

                // 需要平均计算每个原价

                // 套餐子菜价格处理
                // 子菜 原价总和
                $packageTotalOriginPrice = 0;
                foreach ($detail['packageList'] as $pgSon) {
                    $packageTotalOriginPrice += $pgSon['price'] * $pgSon['num'] * $detailNum;
                }

                $detailTotalPrice               = 0;
                $detailTotalTotalDiscount       = 0;
                $detailTotalCouponCostPrice     = 0;
                $detailTotalOrderDiscountPrice  = 0;
                $detailTotalCouponDiscountPrice = 0;

                $detailTotalDiscountPrice = 0;

                $detailCount = count($detail['packageList']);

                // 获取套餐内的子菜名字
                $detailSonList = $detail['packageList'];
                $detailSonList = array_column($detailSonList, null, 'productId');

                // 处理每个价格
                foreach ($detail['packageList'] as $pgSon) {
                    // 获取原价占比
                    $pgSonOriginPercent = bcdiv($pgSon['price'] * $pgSon['num'] * $detailNum, $packageTotalOriginPrice,
                        6);


                    // 最后一个 优惠= 总优惠-前面的优惠
                    if ($foreachNum + 1 == $detailCount) {
                        // 确定的计算
                        $originalPrice            = bcsub($detailOriginPrice, $detailTotalPrice);
                        $totalDiscountPrice       = bcsub($detailTotalDiscount, $detailTotalTotalDiscount);
                        $totalCouponCostPrice     = bcsub($detailCouponCostPrice, $detailTotalCouponCostPrice);
                        $totalOrderDiscountPrice  = bcsub($detailOrderDiscountPrice, $detailTotalOrderDiscountPrice);
                        $totalCouponDiscountPrice = bcsub($detailCouponDiscountPrice, $detailTotalCouponDiscountPrice);

                    } else {
                        // 确定的计算
                        $originalPrice            = bcmul($detailOriginPrice, $pgSonOriginPercent);
                        $totalDiscountPrice       = bcmul($detailTotalDiscount, $pgSonOriginPercent);
                        $totalCouponCostPrice     = bcmul($detailCouponCostPrice, $pgSonOriginPercent);
                        $totalOrderDiscountPrice  = bcmul($detailOrderDiscountPrice, $pgSonOriginPercent);
                        $totalCouponDiscountPrice = bcmul($detailCouponDiscountPrice, $pgSonOriginPercent);
                    }

                    $detailTotalPrice               = bcadd($detailTotalPrice, $originalPrice);
                    $detailTotalTotalDiscount       = bcadd($detailTotalTotalDiscount, $totalDiscountPrice);
                    $detailTotalCouponCostPrice     = bcadd($detailTotalCouponCostPrice, $totalCouponCostPrice);
                    $detailTotalOrderDiscountPrice  = bcadd($detailTotalOrderDiscountPrice, $totalOrderDiscountPrice);
                    $detailTotalCouponDiscountPrice = bcadd($detailTotalCouponDiscountPrice, $totalCouponDiscountPrice);

                    $payPrice = bcsub($originalPrice, $totalDiscountPrice);

                    $detailDiscountType = $detail['discountType'];
                    $discountNum        = $detail['discountNum'];
                    $discountPrice      = 0;
                    if ($detailDiscountType > 0) {
                        // 最后一个 优惠= 总优惠-前面的优惠
                        if ($foreachNum + 1 == $detailCount) {
                            $discountPrice = bcsub($detailDiscountPrice, $detailTotalDiscountPrice);
                        } else {
                            $discountPrice = bcmul($detailDiscountPrice, $pgSonOriginPercent);
                        }
                        $detailTotalDiscountPrice = bcadd($detailTotalDiscountPrice, $discountPrice);

                        if ($detailDiscountType == CodeEnum::ORDER_DISCOUNT_TYPE_ANNUL) {
                            $discountNum = $discountPrice;
                        } else {
                            $discountNum = $detail['discountNum'];
                        }
                    }

                    // 判断赠菜
                    $freeNum = 0;
                    $num     = $pgSon['num'] * $detailNum;
                    if ($detailFreeNum > 0) {
                        $freeNum = $num;
                    }

                    $filterDetail = [
                        'detailId'                 => SnowFlake::nextIdCache(),
                        'orderId'                  => $detail['orderId'] ?? 0,
                        'productId'                => $pgSon['productId'],
                        'num'                      => $num,
                        'originalUnitPrice'        => bcdiv($originalPrice, $num),
                        'originalPrice'            => $originalPrice,
                        'freeNum'                  => $freeNum,
                        'shouldUnitPrice'          => bcdiv($payPrice, $num),
                        'payPrice'                 => $payPrice,
                        'packageUnitNum'           => $pgSon['num'],
                        'packageUnitOriginalPrice' => $pgSon['price'],
                        'packageOriginalPrice'     => $pgSon['num'] * $pgSon['price'],
                        'discountType'             => $detailDiscountType,
                        'discountNum'              => $discountNum,
                        'discountPrice'            => $discountPrice,
                        'totalDiscountPrice'       => $totalDiscountPrice,
                        'orderDiscountPrice'       => $totalOrderDiscountPrice,
                        'couponDiscountPrice'      => $totalCouponDiscountPrice,
                        'couponCostPrice'          => $totalCouponCostPrice,
                        'refundNum'                => $detailRefundNum,
                        'packageId'                => $detail['productId'],
                        'packageDetailId'          => $detail['id'],
                        'type'                     => ProductCode::PRODUCT_TYPE_SINGLE,
                        'menuId'                   => $pgSon['menuId'],
                        'name'                     => $detailSonList[$pgSon['productId']]['productName']
                    ];

                    $sonList[] = $this->formatOrderDetailToFilterDetail($filterDetail, true);

                    $foreachNum++;
                }
            }
        }

        $arr = array_merge($arr, $sonList);
        $this->setOrderFilterOrderDetail($arr, true);
    }

    /**
     * 计算 最后处理订单应付金额 价格
     *
     */
    private function calcOrderShouldPrice()
    {
        // 获取券包信息
        $info = $this->getCouponPackageInfo();
        if (!empty($info)) {
            $this->orderShouldPrice = bcadd($this->orderShouldPrice, $info['salePrice']);
        }

        $this->orderTotalShouldPrice = $this->orderShouldPrice; //复制订单总应收
    }

    /**
     * 计算 整单 V3
     *
     * @throws OrderException
     */
    private function calcAll()
    {
        try {
            if (!empty($this->orderPosDiscount)
                && !empty($this->orderPosDiscount['discountType'])
                && $this->orderPosDiscount['discountType'] > 0) {

                // 进入方法时的应付价格
                $orgOrderShouldPrice = $this->orderShouldPrice;

                // 整单折扣类型
                $orderDiscountType = $this->orderPosDiscount['discountType'];
                switch ($orderDiscountType) {
                    case CodeEnum::ORDER_DISCOUNT_TYPE_COUNT:
                        // 计算后的应付金额
                        $totalShouldPrice =
                            OrderService::bcDiv(
                                bcmul(
                                    $this->orderShouldPrice,
                                    $this->orderPosDiscount['discountNum']
                                )
                            );
                        break;
                    case CodeEnum::ORDER_DISCOUNT_TYPE_ANNUL:
                        $totalShouldPrice =
                            bcsub(
                                $this->orderShouldPrice,
                                $this->orderPosDiscount['discountNum']
                            );
                        break;
                    default:
                        throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR, '整单 折扣/减免类型异常');
                }

                // 判断见面金额是否大于订单应付
                if (bccomp($totalShouldPrice, 0) < 0) {
                    throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR,
                        "整单优惠的金额{$this->orderPosDiscount['discountNum']} 大于 订单应付{$this->orderShouldPrice}");
                }

                // 优惠的金额
                $totalDiscount = $this->orderShouldPrice - $totalShouldPrice;
                // 设置应付金额
                $this->orderShouldPrice = $totalShouldPrice;
                // 计算总优惠金额
                $this->orderDiscountPrice = bcadd($this->orderDiscountPrice, $totalDiscount);
                // 修改订单优惠的金额
                $this->orderPosDiscount['discountPrice'] = $totalDiscount;

                /** 计算平均单个商品的优惠金额 - 开始 **/
                $this->calcProductAvgDiscount($totalDiscount, $orgOrderShouldPrice, 'orderDiscountPrice');
                /** 计算平均单个商品的优惠金额 - 结束 **/

            }
        } catch (OrderException $exception) {
            throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR, '整单折扣计算异常');
        }
    }

    /**
     * 计算 整单优惠的每个商品的平均价格优惠
     *
     * @param $totalDiscount mixed 总优惠
     * @param $orderShouldPrice mixed 应付
     */
    private function calcProductAvgDiscount($totalDiscount, $orderShouldPrice, $parma, $isCalcDiscount = true)
    {
        if ((int)$totalDiscount <= 0 || (int)$orderShouldPrice <= 0) {
            return;
        }

        if (bccomp($totalDiscount, $orderShouldPrice) > 0) {
            $totalDiscount = $orderShouldPrice;
        }

        // 算出优惠占比
        $percent = bcdiv($totalDiscount, $orderShouldPrice, 6);
        $arr     = $this->getOrderFilterOrderDetail();

        // 数组排序
        $arr = OrderService::arraySort($arr, 'payPrice', SORT_ASC);

        $detailCount              = count($arr);
        $detailTotalDiscountPrice = 0;
        $foreachNum               = 0;

        foreach ($arr as &$detail) {
            $discountPrice = round(bcmul($percent, $detail['payPrice'], 9)); // 四舍五入取整

            // 最后一个 优惠= 总优惠-前面的优惠
            if ($foreachNum + 1 == $detailCount) {
                $discountPrice = bcsub($totalDiscount, $detailTotalDiscountPrice);
            }
            $detailTotalDiscountPrice = bcadd($detailTotalDiscountPrice, $discountPrice);

            // 整单优惠的金额计算
            $detail[$parma] = bcadd($detail[$parma], $discountPrice);
            if ($isCalcDiscount) {
                $detail['totalDiscountPrice'] = bcadd($detail['totalDiscountPrice'], $discountPrice);
                $detail['payPrice']           = bcsub($detail['payPrice'], $discountPrice);
                $detail['shouldUnitPrice']    = bcdiv($detail['payPrice'], $detail['num']);
            }

            $foreachNum++;
        }

        unset($detail);

        $this->setOrderFilterOrderDetail($arr, true);
    }


    /**
     * 处理自身优惠券
     *
     * @throws OrderException
     */
    private function calcGoodsCoupon()
    {
        try {
            if (!empty($this->getOrderUserCoupon())) {
                // 处理逻辑
                $couponList = $this->calcInGoodsCoupon($this->getOrderUserCoupon());
                $this->setOrderUserCoupon($couponList, true);
            }

        } catch (OrderException $exception) {
            throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR, $exception->message);
        }
    }

    /**
     * 处理自身优惠券
     *
     * @throws OrderException
     */
    private function calcCoupon()
    {
        try {
            // 进入方法时的应付价格
            $orgOrderShouldPrice = $this->orderShouldPrice;

            if (!empty($this->getOrderPosCoupon())) {
                // 处理逻辑
                $couponList = $this->calcInCoupon($this->getOrderPosCoupon(), $orgOrderShouldPrice);
                $this->setOrderPosCoupon($couponList, true);

            }

            if (!empty($this->getOrderUserCoupon())) {
                // 处理逻辑
                $couponList = $this->calcInCoupon($this->getOrderUserCoupon(), $orgOrderShouldPrice);
                $this->setOrderUserCoupon($couponList, true);
            }

        } catch (OrderException $exception) {
            throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR, $exception->message);
        }
    }

    /**
     * 处理 订单支付 金额问题
     *
     * @throws OrderException
     */
    private function calcPayment()
    {
        try {
            // 进入方法时的应付价格
            $orgOrderShouldPrice = $this->orderShouldPrice;

            $orderPayment = $this->getOrderPaymentList();

            if (!empty($orderPayment)) {
                // 判断应付金额是否 大于 需要付款金额
                $totalPaymentTotalPrice = 0; // 总支付金额
                // 处理计入 优惠的金额
                $totalDiscountPrice = 0;
                foreach ($orderPayment as $payment) {
                    // 判断是否有新增自定义支付, 如果没有 跳过校验
                    if ($payment['payMoney'] == -1) {
                        continue;
                    }

                    $totalPaymentTotalPrice += $payment['payMoney'];

                    if (empty($payment['id']) && $totalPaymentTotalPrice > $orgOrderShouldPrice) {
                        throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR,
                            '操作失败：自定义支付金额大于应付金额。');
                    }

                    if ($payment['reckonType'] == CustomPaymentEnum::RECKON_TYPE_DISCOUNT) {
                        $totalDiscountPrice += $payment['payMoney'];
                    }
                }

                // 处理每个优惠
                $this->calcProductAvgDiscount($totalDiscountPrice, $orgOrderShouldPrice, 'orderDiscountPrice');
                // 订单应付
                $this->orderShouldPrice = bcsub($this->orderShouldPrice, $totalPaymentTotalPrice);
                // 订单优惠
                $this->orderDiscountPrice = bcadd($this->orderDiscountPrice, $totalDiscountPrice);

                // 处理剩余金额
                foreach ($orderPayment as &$payment) {
                    if ($payment['payMoney'] == -1) {
                        // 处理每个优惠
                        $this->calcProductAvgDiscount($this->orderShouldPrice, $this->orderShouldPrice,
                            'orderDiscountPrice');
                        // 订单优惠
                        $this->orderDiscountPrice = bcadd($this->orderDiscountPrice, $this->orderShouldPrice);
                        $payment['payMoney']      = $this->orderShouldPrice;
                        // 订单应付
                        $this->orderShouldPrice = bcsub($this->orderShouldPrice, $this->orderShouldPrice);
                        break;
                    }
                }

                // 塞入自定义支付的列表
                $customPayment = [];
                foreach ($orderPayment as $item) {
                    if ($item['orderPaymentType'] == CodeEnum::ORDER_PAYMENT_TYPE_CUSTOM_PAYMENT) {
                        $customPayment[] = $item;
                    }
                }

                $this->setOrderCustomPaymentList($customPayment);
            }

        } catch (OrderException $exception) {
            throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR, $exception->message);
        }
    }


    /**
     * 处理商品券优惠券 的 逻辑
     */
    private function calcInGoodsCoupon($couponList)
    {
        $totalCouponDiscount  = 0;
        $totalCouponCostPrice = 0;

        foreach ($couponList as &$coupon) {

            if ($coupon['couponType'] != CouponEnum::COUPON_TYPE_EXCHANGE) {
                continue;
            }


            // 暂时不支持全部商品
            if ($coupon['canUseProduct'] == 0) {
                throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR,
                    '商品券有异常无法使用, 不能核销, 请联系门店运营人员调整');
            }

            $productList = explode(',', $coupon['canUseProduct']);
            // 可用商品大于1提示异常
            if (count($productList) != 1) {
                throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR,
                    '商品券产品数量过多无法使用, 不能核销, 请联系门店运营人员调整');
            }

            $firstOne = reset($productList);

//                    // 判断是否是再结账
//                $isPayAgain = false;
//                    foreach ($this->orderFilterOrderDetail as $detail) {
//
//                    }

            $couponDiscountPrice = 0;

            foreach ($this->orderFilterOrderDetail as &$detail) {
                // 找到第一个数量 1 的 非 赠菜
                if ($detail['productId'] == $firstOne && $detail['freeNum'] == 0) {
                    $detail['totalDiscountPrice']  = $detail['originalPrice']; // 总优惠 = 总原价
                    $detail['couponDiscountPrice'] = $couponDiscountPrice = $detail['shouldUnitPrice']; // 券优惠 = 折后单价

                    $detail['couponCostPrice'] = 0; // 商品券成本为 0
                    $detail['shouldUnitPrice'] = 0; // 折后单价 置为 0
                    $detail['payPrice']        = bcmul(
                        bcsub($detail["num"], 1),
                        $detail["originalUnitPrice"]
                    ); // 应付也应该为 0
                    break;
                }
            }

            // 券 总优惠
            $this->couponPrice = bcadd($this->couponPrice, $couponDiscountPrice);
            // 券 的成本总额
            $this->couponCostPrice = bcadd($this->couponCostPrice, $coupon['costPrice']);
            // 订单总优惠
            $this->orderDiscountPrice = bcadd($this->orderDiscountPrice, $couponDiscountPrice);
            // 订单应付 = 订单应付 - 优惠券优惠金额
            $this->orderShouldPrice = $this->setPriceNotNegative(
                bcsub($this->orderShouldPrice, $couponDiscountPrice)
            );
            // 菜品券优惠累加
            $this->kfExchangeDiscount      = bcadd($this->kfExchangeDiscount, $couponDiscountPrice);
            $totalCouponDiscount           = bcadd($totalCouponDiscount, $couponDiscountPrice);
            $totalCouponCostPrice          = bcadd($totalCouponCostPrice, $coupon['costPrice']);
            $coupon['couponDiscountPrice'] = $couponDiscountPrice;
        }


        // 商品券不用计算均摊数据
        /** 计算平均单个商品的优惠金额 - 开始 **/
//        $this->calcProductAvgDiscount($totalCouponCostPrice, $orgOrderShouldPrice, 'couponCostPrice', false);
//        $this->calcProductAvgDiscount($totalCouponDiscount, $orgOrderShouldPrice, 'couponDiscountPrice');
        /** 计算平均单个商品的优惠金额 - 结束 **/
        return $couponList;
    }

    /**
     * 处理优惠券 的 逻辑
     */
    private function calcInCoupon($couponList, $orgOrderShouldPrice)
    {
        $totalCouponDiscount  = 0;
        $totalCouponCostPrice = 0;


        foreach ($couponList as &$coupon) {
            switch ($coupon['couponType']) {
                // 代金券
                case CouponEnum::COUPON_TYPE_VOUCHERS:
                    $couponDiscountPrice = self::getSmallNum($this->orderShouldPrice, $coupon['discount']);
                    // 判断券的成本是否大于订单应付
                    if (bccomp($coupon['costPrice'], $this->orderShouldPrice) > 0) {
                        throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR,
                            '优惠券实收大于订单应付, 不能核销, 请核对所选菜品与团购券是否匹配。点餐请在对应的团购分类菜单里进行操作（快速核销/点选菜品-更多-验券）');
                    }

                    break;
                // 折扣券
                case CouponEnum::COUPON_TYPE_DISCOUNT:
                    // 折扣券无成本,不考虑成本问题
                    // 订单折扣金额
                    $couponDiscountPrice =
                        OrderService::bcDiv(bcmul($this->orderShouldPrice, 100 - $coupon['discount']), 'ceil');

                    // 判断金额大小, 不大于最大抵扣金额
                    if ($coupon['cappedAmount'] > 0) {
                        $couponDiscountPrice = self::getSmallNum($couponDiscountPrice, $coupon['cappedAmount']);
                    }
                    break;
                // 商品券
                case CouponEnum::COUPON_TYPE_EXCHANGE:
                    continue 2;
//                    // 暂时不支持全部商品
//                    if ($coupon['canUseProduct'] == 0) {
//                        throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR, '商品券有异常无法使用, 不能核销, 请联系门店运营人员调整');
//                    }
//
//                    $productList = explode(',', $coupon['canUseProduct']);
//                    // 可用商品大于1提示异常
//                    if (count($productList) != 1) {
//                        throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR,
//                            '商品券产品数量过多无法使用, 不能核销, 请联系门店运营人员调整');
//                    }
//
//                    $firstOne = reset($productList);
//
//                    // 判断是否是再结账
////                $isPayAgain = false;
////                    foreach ($this->orderFilterOrderDetail as $detail) {
////
////                    }
//
//                    foreach ($this->orderFilterOrderDetail as &$detail) {
//                        // 找到第一个数量 1 的 非 赠菜
//                        if ($detail['productId'] == $firstOne && $detail['freeNum'] == 0 && $detail['num'] == 1) {
//                            $detail['totalDiscountPrice']  = $detail['originalPrice']; // 总优惠 = 总原价
//                            $detail['couponDiscountPrice'] = $detail['shouldUnitPrice']; // 券优惠 = 折后单价
//                            $coupon['couponDiscountPrice'] = $detail['shouldUnitPrice'];
//
//                            $detail['couponCostPrice'] = 0; // 券优惠 = 折后单价
//                            $detail['shouldUnitPrice'] = 0; // 折后单价 置为 0
//                            $detail['payPrice']        = 0;
//
//                        }
//                    }
//
//                    // 商品券单独处理
//                    continue 2;
//
//                    break;
            }


            // 券 总优惠
            $this->couponPrice = bcadd($this->couponPrice, $couponDiscountPrice);
            // 券 的成本总额
            $this->couponCostPrice = bcadd($this->couponCostPrice, $coupon['costPrice']);
            // 订单总优惠
            $this->orderDiscountPrice = bcadd($this->orderDiscountPrice, $couponDiscountPrice);
            // 订单应付 = 订单应付 - 优惠券优惠金额
            $this->orderShouldPrice        = $this->setPriceNotNegative(
                bcsub($this->orderShouldPrice, $couponDiscountPrice)
            );
            $totalCouponDiscount           = bcadd($totalCouponDiscount, $couponDiscountPrice);
            $totalCouponCostPrice          = bcadd($totalCouponCostPrice, $coupon['costPrice']);
            $coupon['couponDiscountPrice'] = $couponDiscountPrice;
        }

        $this->kfNonExchangeDiscountPrice = bcsub($this->couponPrice, $this->kfExchangeDiscount);
        /** 计算平均单个商品的优惠金额 - 开始 **/
        $this->calcProductAvgDiscount($totalCouponCostPrice, $orgOrderShouldPrice, 'couponCostPrice', false);
        $this->calcProductAvgDiscount($totalCouponDiscount, $orgOrderShouldPrice, 'couponDiscountPrice');
        /** 计算平均单个商品的优惠金额 - 结束 **/
        return $couponList;
    }

    /**
     * 处理三方优惠券
     *
     * @throws OrderException
     * @throws CouponExtException
     * @throws GuzzleException|BaseException
     */
    private function calcExtCoupon()
    {
        try {
            // 进入方法时的应付价格
            $orgOrderShouldPrice = $this->orderShouldPrice;

//            dump('进来时的价格:' . $orgOrderShouldPrice);

            // 判断是否有需要 退的优惠券
            if (!empty($this->orderPosCouponExtRefund)) {
                foreach ($this->orderPosCouponExtRefund as $refundCoupon) {
                    $this->setNeedRefundExtCoupon($refundCoupon);
                }
            }

            // 判断是否需要 计算三方优惠券
            if (!empty($this->orderPosCouponExt)) {
                // 获取原有订单核销了的三方优惠券
                $orgExtCoupon = $this->getOrderExtCoupon();

                // 映射数据库中的 字段
                foreach ($orgExtCoupon as &$inCoupon) {
                    $couponExtCodeData          = json_decode($inCoupon['couponExtCodeData'], true);
                    $inCoupon['deduction']      = $inCoupon['couponExtDiscountPrice'];
                    $inCoupon['code']           = $inCoupon['couponExtCode'];
                    $inCoupon['userPay']        = $inCoupon['couponExtCostPrice'];
                    $inCoupon['canUseProducts'] = $couponExtCodeData['canUseProducts'] ?? [];
                }

                $arrOrgDiscount = array_column($orgExtCoupon, null, 'couponExtCode');

                $totalExtCouponDiscount  = 0;
                $totalExtCouponCostPrice = 0;

                foreach ($this->orderPosCouponExt as &$coupon) {
                    $needCheck = false;
                    // 为了兼容以前的代码 留个口子
                    $num       = $coupon['num'] ?: 1;
                    $queryCode = empty($coupon['codeUrl']) ? ($coupon['code'] ?? '') : $coupon['codeUrl'];

                    // 判断之前是否有过
                    if (!empty($queryCode) && key_exists($queryCode, $arrOrgDiscount)) {
                        $couponInfo = $arrOrgDiscount[$queryCode];
                    } else {
                        $orderCouponExt = new OrderCouponExternalService(0, $this->shopId, $this->brandId);
                        $orderCouponExt->resetCouponExtPlatformByPosPayment($coupon['type'] ?? 0);
                        // 下面的代码测试用
//                        $couponInfo = Cache::remember('getCouponExtInfo:' . $queryCode, 3600, function () use (
//                            $orderCouponExt,
//                            $queryCode
//                        ) {
//                            return $orderCouponExt->getCouponExtInfo($queryCode);
//                        });
                        $couponInfo = $orderCouponExt->getCouponExtInfo($queryCode, true);
                        $needCheck  = true;
                    }

                    // 判断是否需要优惠金额关联商品
                    $useProductsList = $coupon['useProducts']['list'] ?? [];
                    if (!empty($couponInfo['canUseProducts']['list'])
                        && count($couponInfo['canUseProducts']['list']) >= 1
                        && count($useProductsList) >= 1
                    ) {
//                        dump($useProductsList);
                        // 找到符合券商品的第一个菜品获取他的优惠金额
                        $filterProductList = $this->getOrderDetailWithCouponCanUseProducts($couponInfo['canUseProducts']['list'],
                            $useProductsList);

                        //

//                        dump($filterProductList);

                        if (!empty($filterProductList)) {
                            // 重新计算优惠金额
                            $reCalcDeduction = $this->reCalcCouponDeduction($filterProductList);

//                            dump($reCalcDeduction);
//                            dump($reCalcDeduction, $couponInfo);
                            // TODO 这里其实需要做个报错,具体看业务情况是否需要异常中断 ↓
                            if (bccomp($reCalcDeduction, $couponInfo['userPay']) < 0) {
                                $diffPrice = bcdiv100(bcsub($couponInfo['userPay'], $reCalcDeduction));

                                throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR,
                                    '优惠金额超出实收' . $diffPrice . '元，请撤销优惠操作。');
//                                $reCalcDeduction = $couponInfo['userPay'];
                            }

                            $couponInfo['deduction'] = bcdiv($reCalcDeduction, $num);

                            // 存储优惠券自定义数据 customData 会落库
                            $coupon['customData']['useProducts']              = $coupon['useProducts'];
                            $coupon['customData']['useProducts']['list']      = self::calcSingleUnit($coupon['customData']['useProducts']['list'],
                                $num);
                            $coupon['customData']['useProducts']['deduction'] = $couponInfo['deduction'];
                        }
                    }

//                    dump($coupon);
                    // 单张 优惠券抵扣金额
//                    $deduction = bcdiv($couponInfo['deduction'], $num);
                    $deduction = $couponInfo['deduction'];
                    // 单张 优惠券的成本;
                    $userPay = $couponInfo['userPay'];
                    // 此次循环总优惠金额
                    $loopDiscount = bcmul($num, $deduction);
                    // 此次循环的总券收入
                    $loopCostPrice = bcmul($num, $userPay);

//                    dump($loopDiscount);
                    // 判断订单应付是否大于优惠券面值
                    $couponDiscountPrice = self::getSmallNum($this->orderShouldPrice, $loopDiscount);

//                    dump($couponDiscountPrice);

                    // 判断券的成本是否大于订单应付
                    if (bccomp($loopCostPrice, $this->orderShouldPrice) > 0) {
                        $diffPrice = bcdiv100(bcsub($loopCostPrice, $this->orderShouldPrice));

                        throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR,
                            "订单金额未达到优惠券额度，请提醒顾客加购 {$diffPrice} 元");
                    }

                    // 校验最多张数1
                    $totalDiscount = bcsub($loopDiscount, $loopCostPrice);

                    if ($num > 1 &&
                        bccomp($totalDiscount, $userPay) < 0 &&
                        $totalDiscount > 0 &&
                        bccomp($loopDiscount, $orgOrderShouldPrice) > 0
                    ) {
                        $lastMinTotalDiscount = bcadd(bcmul($num - 1, $deduction), $userPay);
                        if (bccomp($orgOrderShouldPrice, $lastMinTotalDiscount) < 0) {
                            $showNum = $num - 1;
                            throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR,
                                '优惠券金额溢出, 最多使用' . $showNum . '张');
                        }

                    }

                    // 不要随便动这里的计算逻辑 否则多券核销会异常!!!
                    for ($i = 1; $i <= $num; $i++) {
                        // 计算出溢出的张数
                        // 此次循环总优惠金额
                        $forDiscount = bcmul($i, $deduction);
                        $checkPrice  = $orgOrderShouldPrice - $forDiscount;

                        if ($num != 1 && $checkPrice < 0 && abs($checkPrice) > $userPay) {
                            $limitNum = $i - 1;
                            throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR,
                                '优惠券金额溢出, 最多使用' . $limitNum . '张.');
                        }
                    }

                    // 券 总优惠
                    $this->couponPrice = bcadd($this->couponPrice, $couponDiscountPrice);
                    // 券 的成本总额
                    $this->couponCostPrice = bcadd($this->couponCostPrice, $loopCostPrice);
                    // 订单总优惠
                    $this->orderDiscountPrice = bcadd($this->orderDiscountPrice, $couponDiscountPrice);
                    // 订单应付 = 订单应付 - 优惠券优惠金额
                    $this->orderShouldPrice = $this->setPriceNotNegative(
                        bcsub($this->orderShouldPrice, $couponDiscountPrice)
                    );

                    $totalExtCouponDiscount  = bcadd($totalExtCouponDiscount, $couponDiscountPrice);
                    $totalExtCouponCostPrice = bcadd($totalExtCouponCostPrice, $loopCostPrice);

                    $this->extDiscountPrice = bcadd($this->extDiscountPrice,
                        bcmul($couponInfo['deduction'] ?? 0, $coupon['num']));

                    $coupon['couponDiscountPrice'] = $couponDiscountPrice;


                    if ($needCheck) {
                        $this->setNeedCheckExtCoupon($coupon);
                    }
                }

                /** 计算平均单个商品的优惠金额 - 开始 **/
                $this->calcProductAvgDiscount($totalExtCouponCostPrice, $orgOrderShouldPrice, 'couponCostPrice',
                    false);
                $this->calcProductAvgDiscount($totalExtCouponDiscount, $orgOrderShouldPrice, 'couponDiscountPrice');
                /** 计算平均单个商品的优惠金额 - 结束 **/
            }
            // 判断 不在需要核销的 优惠券 ,但是库里存在的优惠券 列为需要退款的券信息

//            dd($this->orderShouldPrice);

        } catch (OrderException $exception) {
            throw new OrderException(OrderCodeEnum::ERR_ORDER_ADD_ERROR,
                '核销三方优惠券有误,原因:' . $exception->getMessage(),
                $exception);
        }
    }

    /**
     * 统一计价 V3
     *
     * @return void
     * @throws OrderException
     * @throws CouponExtException
     * @throws GuzzleException
     */
    public function calcV3()
    {
        // 计价 单品
        $this->calcOne();
        // 计算 商家自配送, 渠道配送
        $this->calcSelfDelivery();
        // 计算 商品券-前置
        $this->calcGoodsCoupon();
        // 计价 整单
        $this->calcAll();
        // 计算 内部优惠券
        $this->calcCoupon();
        // 计算 三方优惠券
        $this->calcExtCoupon();
        // 计算 支付方式的金额
        $this->calcPayment();
        // 计价 套餐子商品的优惠金额
        $this->calcPackage();
        // 计算 应付金额
        $this->calcOrderShouldPrice();
    }


    /**
     * 自定义 - 简化计价
     *
     * @return void
     * @throws CouponExtException
     * @throws GuzzleException
     * @throws OrderException
     *
     * 计价 单品
     * $this->calcOne();
     * 计算 商家自配送, 渠道配送
     * $this->calcSelfDelivery();
     * 计算 商品券-前置
     * $this->calcGoodsCoupon();
     * 计价 整单
     * $this->calcAll();
     * 计算 内部优惠券
     * $this->calcCoupon();
     * 计算 三方优惠券
     * $this->calcExtCoupon();
     * 计算 支付方式的金额
     * $this->calcPayment();
     * 计价 套餐子商品的优惠金额
     * $this->calcPackage();
     * 计算 应付金额
     * $this->calcOrderShouldPrice();
     *
     */
    public function calcCustomer(array $func = [])
    {
        foreach ($func as $f) {
            $this->$f();
        }
    }

    /**
     * 返回数字小的那个
     *
     * @param $firstNum
     * @param $secondNum
     * @return string
     */
    public static function getSmallNum($firstNum, $secondNum): string
    {
        if (bccomp($firstNum, $secondNum) >= 0) {
            return $secondNum;
        } else {
            return $firstNum;
        }
    }

    /**
     * 处理 订单 应付的金额问题
     */
    public function calcPosRefundPrice(): int
    {
        $orderShouldPrice = $this->orderShouldPrice;

        if (empty($this->getOrderRefundPaymentList()[0])) {
            return 0;
        }


        $refundData = $this->getOrderRefundPaymentList()[0];
        if (empty($refundData)) {
            return 0;
        }

        // 判断是否退款 [只允许 全退 或者 退部分]
        $refundShouldPriceStatus = bccomp($orderShouldPrice, 0);
        if ($refundShouldPriceStatus <= 0) {
            // 退款 - 现在只允许 一条记录 退 一次款
            $refundMoney = abs($orderShouldPrice);
            // 判断 退款金额 是否超出金额 上线

            if ($refundShouldPriceStatus == 0 || $refundMoney >= $refundData['payMoney']) {
                $refundMoney = $refundData['payMoney'];
            }
            // 如果全退 需要把 应付金额 加上
            if ($refundShouldPriceStatus == 0) {
                $this->orderShouldPrice = bcadd($this->orderShouldPrice, $refundMoney);
            }

            $refundData['payMoney'] = $refundMoney;
        }

        // 判断 退款路径
        switch ($refundData['orderPaymentType']) {
            case CodeEnum::ORDER_PAYMENT_TYPE_FIAT:
                $this->setOrderUserSidePaymentList($refundData);
                break;
            case CodeEnum::ORDER_PAYMENT_TYPE_CUSTOM_PAYMENT:
                // 判断是否计入实收 如果计入优惠 减了它
                if ($refundData['reckonType'] == CustomPaymentEnum::RECKON_TYPE_DISCOUNT) {
                    $this->orderDiscountPrice = OrderCalcService::setPriceNotNegative(
                        bcsub($this->orderDiscountPrice, $refundData['payMoney']));
                }

                $this->setOrderRefundCustomPaymentList($refundData);
                break;
            default:
                return 0;
        }

        Log::channel('order')->debug('orderCalcService-calcPosRefundPrice', [
            'orderId'          => $this->orderId,
            'refundData'       => $refundData,
            'orderShouldPrice' => $orderShouldPrice,
        ]);

        return $refundData['payMoney'];
    }

    /******** 以 下 为 set / get 方 法 **********/

    /**
     * @param array $coupon
     * @param bool $replace
     */
    public function setOrderUserCoupon(array $coupon, bool $replace = false): void
    {
        if (empty($coupon) && !$replace) {
            return;
        }

        $replace ? $this->orderUserCoupon = $coupon : array_push($this->orderUserCoupon,
            $coupon);
    }

    /**
     * @param string $field
     * @return array
     */
    public function getOrderUserCoupon(string $field = ''): array
    {
        if (empty($field)) {
            return $this->orderUserCoupon;
        } else {
            return array_column($this->orderUserCoupon, $field);
        }
    }

    /**
     * @param array $coupon
     * @param bool $replace
     */
    public function setOrderPosCoupon(array $coupon, bool $replace = false): void
    {
        if (empty($coupon)) {
            return;
        }
        $replace ? $this->orderPosCoupon = $coupon : array_push($this->orderPosCoupon,
            $coupon);
    }

    /**
     * @param string $field
     * @return array
     */
    public function getOrderPosCoupon(string $field = ''): array
    {
        if (empty($field)) {
            return $this->orderPosCoupon;
        } else {
            return array_column($this->orderPosCoupon, $field);
        }
    }

    /**
     * @return string
     */
    public function getOrderOriginalPrice(): string
    {
        return $this->orderOriginalPrice;
    }

    /**
     * @return string
     */
    public function getOrderShouldPrice(): string
    {
        return $this->orderShouldPrice;
    }

    /**
     * @return array
     */
    public function getNeedCheckExtCoupon(): array
    {
        return $this->needCheckExtCoupon;
    }

    /**
     * @param array $needCheckExtCoupon
     * @param bool $replace
     */
    public function setNeedCheckExtCoupon(array $needCheckExtCoupon, bool $replace = false): void
    {
        if (empty($needCheckExtCoupon)) {
            return;
        }
        $replace ? $this->needCheckExtCoupon = $needCheckExtCoupon : array_push($this->needCheckExtCoupon,
            $needCheckExtCoupon);
    }

    /**
     * @return array
     */
    public function getNeedRefundExtCoupon(): array
    {
        return $this->needRefundExtCoupon;
    }

    /**
     * @param array $needRefundExtCoupon
     * @param bool $replace
     */
    public function setNeedRefundExtCoupon(array $needRefundExtCoupon, bool $replace = false): void
    {
        if (empty($needRefundExtCoupon)) {
            return;
        }
        $replace ? $this->needRefundExtCoupon = $needRefundExtCoupon : array_push($this->needRefundExtCoupon,
            $needRefundExtCoupon);
    }

    /**
     * @return array
     */
    public function getOrderPosCouponExt(): array
    {
        return $this->orderPosCouponExt;
    }

    /**
     * @return array
     */
    public function getOrderPosDiscount(): array
    {
        return $this->orderPosDiscount;
    }

    /**
     * @param array $orderPosDiscount
     */
    public function setOrderPosDiscount(array $orderPosDiscount): void
    {
        $this->orderPosDiscount = $orderPosDiscount;
    }

    /**
     * 生成处理过的 OrderDetail
     */
    private function setOrderFilterOrderDetail($orderDetail, $replace = false)
    {
        if ($replace) {
            $this->orderFilterOrderDetail = $orderDetail;
        } else {
            $detailId                       = SnowFlake::nextIdCache();
            $orderDetail['detailId']        = $detailId;
            $this->orderFilterOrderDetail[] = $this->formatOrderDetailToFilterDetail($orderDetail);
        }
    }

    /**
     * 获取菜品库的原始信息
     *
     * @param $id
     * @param $menuId
     * @return array|mixed
     * @throws OrderException
     */
    private function getProductInfo($id, $menuId)
    {
        // 为了满足自定义商品
        $key = $id . '_' . $menuId;
        if (empty($this->orderProductDetail[$key])) {
            $key = $id . '_' . 0;
        }

        $productInfo = $this->orderProductDetail[$key] ?? [];

        if (empty($productInfo)) {
            throw new OrderException(OrderCodeEnum::ERR_ORDER_DISH_MISS,
                $id . '菜品缺失, 请更新菜单后再试, 若还有问题请联系门店运营人员');
        }

        return $productInfo;
    }

    /**
     * 获取菜品库的原始信息
     *
     * @param $id
     * @param $menuId
     * @return array|mixed
     */
    private function getProductInfoFromDb($id, $menuId)
    {
        return Cache::remember('getProductInfoFromDb:' . $menuId . ':' . $id, getSwitchCacheTime(600), function ()
        use ($id, $menuId) {
            return Product::getProductInfoById($id);
        });
    }

    /**
     * 获取所有必点商品
     *
     * @return Collection
     */
    private function getAllMustProduct(): Collection
    {
        return collect($this->orderProductDetail)
            ->where('status', 0)
            ->where('isMust', BaseModel::SWITCH_ON)
            ->pluck('id');
    }

    /**
     * 获取菜品的原始信息
     *
     * @param $id
     * @param string $field
     * @param int $menuId
     * @return mixed
     * @throws OrderException
     */
    private function getProductField($id, string $field = '', int $menuId = 0)
    {
        if (empty($menuId)) {
            $menuId = $this->menuId;
        }

        if (empty($field)) {
            return $this->getProductInfo($id, $menuId);
        }
        return $this->getProductInfo($id, $menuId)[$field] ?? "";
    }

    /**
     * 获取用户修改人
     *
     * @return array|Authenticatable
     * @throws UserException
     */
    private function getModifyUser()
    {
        if ($this->sourceType == CodeEnum::SOURCE_TYPE_MINI) {
            return [];
        } else {
            return AuthService::getAuthUser();
        }
    }

    /**
     * 获取订单价格
     *
     * @return array
     */
    public function getOrderPriceNow(): array
    {
        return [
            // 原价
            'orderOriginPrice'           => $this->orderOriginalPrice,
            // 总应付
            'orderTotalShouldPrice'      => $this->orderTotalShouldPrice,
            // 剩余应付
            'orderShouldPrice'           => $this->orderShouldPrice,
            // 总优惠价格
            'orderDiscountPrice'         => $this->orderDiscountPrice,
            // 优惠券实际优惠的金额
            'orderCouponPrice'           => $this->couponPrice,
            // 优惠券的总收入
            'orderCouponCostPrice'       => $this->couponCostPrice,
            // 订单实际优惠
            'realDiscountPrice'          => bcsub($this->orderDiscountPrice, $this->couponCostPrice),
            //夸父商品券折扣优惠
            'kfExchangeDiscount'         => $this->kfExchangeDiscount,
            //夸父非商品券折扣优惠
            'kfNonExchangeDiscountPrice' => $this->kfNonExchangeDiscountPrice,
        ];
    }

    /**
     * 获取 orderDetail
     * @return array
     */
    public function getOrderFilterOrderDetail(): array
    {
        return $this->orderFilterOrderDetail;
    }

    /**
     * 设置数据库的订单原数据
     *
     * @throws OrderException
     */
    private function setOrderProductDetail(int $isRefund)
    {
        $this->orderProductDetail = ProductShopService::getProductListInstance($this->shopId);

        if ($isRefund == CodeEnum::ORDER_PAY_EVENT_REFUND) {
            return;
        }

        try {
            // 判断状态
            foreach ($this->productIds as $pid) {
                $pInfo = $this->getProductInfo($pid, $this->menuId);

                // 这里不应该出现套餐的子菜数据
                if (!empty($pInfo['packageId'])) {
                    throw new OrderException(OrderCodeEnum::ERR_ORDER_DISH_MISS,
                        '菜品数据异常(1), 请更新菜单后再试, 若还有问题请联系门店运营人员');
                }

                if ($pInfo['status'] == DBEnum::STATUS_DOWN) {
                    throw new OrderException(OrderCodeEnum::ERR_ORDER_DISH_MISS,
                        '菜品已下架, 请更新菜单后再试, 若还有问题请联系门店运营人员');
                }

                if ($pInfo['out'] == DBEnum::STATUS_DOWN) {
                    throw new OrderException(OrderCodeEnum::ERR_ORDER_DISH_MISS,
                        '菜品已估清, 请更新菜单后再试, 若还有问题请联系门店运营人员');
                }
            }

        } catch (\Exception $exception) {
            Log::channel('order')->error('菜品数据异常(0-9)', [
                'pid'   => $pid,
                'pInfo' => $pInfo ?? [],
            ]);

            $name = ($pInfo['name'] ?? '') . "({$pid})";

            if ($exception->getCode() == OrderCodeEnum::ERR_ORDER_DISH_MISS) {
                // 缺失菜品
                $orgInfo = $this->getProductInfoFromDb($pid, $this->menuId);
                $msg     = $orgInfo->name . $exception->getMessage();
            } else {
                $msg = $name . ', 菜品已有异常(0), 请更新菜单后再试, 若还有问题请联系门店运营人员';
            }

            throw new OrderException(OrderCodeEnum::ERR_ORDER_DISH_MISS, $msg);
        }


    }

    /**
     * 获取用户订单所有的优惠券  // TODO 拆分出去
     *
     * @return array
     * @throws OrderException
     */
    public function getUserOrderAllCoupon(): array
    {
        $productList = $this->getProductLists();

        // 格式化 传入的优惠券列表
        $usedCouponList = [];

        foreach ($this->getOrderUserCoupon() as $posCoupon) {
            $usedCouponList[] = [
                'couponCode' => $posCoupon['couponCode'] ?? 0,
            ];
        }

        $customerCouponService = CustomerCouponService::getUserCanUseCouponList(
            $this->brandId,
            $this->shopId,
            $this->userId,
            $productList,
            $usedCouponList,
            $this->sourceType,
            true
        );

        return [
            'disableCouponList' => $customerCouponService->getCanNotUseCouponList(),
            'enableCouponList'  => $customerCouponService->getCanUseCouponList()
        ];
    }

    /**
     * 返回统一的数据格式
     *
     * @param array $data
     * @param bool $isPackage
     * @return array
     * @throws OrderException
     */
    public function formatOrderDetailToFilterDetail(array $data, bool $isPackage = false): array
    {
        return [
            'id'                       => $data['detailId'],
            'orderId'                  => $data['orderId'] ?? 0,
            'productId'                => $data['productId'],
            'productName'              => empty($data['name']) ? $this->getProductField($data['productId'],
                'name', $data['menuId'] ?? 0) : $data['name'],
            'num'                      => $data['num'],
            'unit'                     => $this->getProductField($data['productId'], 'unit',
                    $data['menuId'] ?? 0) ?? '',
            'originalUnitPrice'        => $data['originalUnitPrice'],
            'originalPrice'            => $data['originalPrice'],
            'shouldUnitPrice'          => $data['shouldUnitPrice'],
            'payPrice'                 => $data['payPrice'],
            'discountType'             => $data['discountType'],
            'discountNum'              => $data['discountNum'],
            'discountPrice'            => $data['discountPrice'],
            'totalDiscountPrice'       => $data['totalDiscountPrice'],
            'orderDiscountPrice'       => $data['orderDiscountPrice'],
            'couponDiscountPrice'      => $data['couponDiscountPrice'],
            'couponCostPrice'          => $data['couponCostPrice'],
            'freeNum'                  => $data['freeNum'],
            'imageUrl'                 => $this->getProductField($data['productId'], 'imageUrl', $data['menuId'] ?? 0),
            'refundNum'                => $data['refundNum'],
            'categoryId'               => $this->getProductField($data['productId'], 'categoryId',
                    $data['menuId'] ?? 0) ?? 0,
            'type'                     => $data['type'],
            'packageId'                => $data['packageId'] ?? 0,
            'packageDetailId'          => $data['packageDetailId'] ?? 0,
            'packageList'              => $data['packageList'] ?? [],
            'packageUnitNum'           => $data['packageUnitNum'] ?? 0,
            'packageUnitOriginalPrice' => $data['packageUnitOriginalPrice'] ?? 0,
            'packageOriginalPrice'     => $data['packageOriginalPrice'] ?? 0,
            'menuId'                   => $this->getProductField($data['productId'], 'menuId', $data['menuId'] ?? 0),
            'remark'                   => $data['remark'] ?? ''
        ];
    }

    /**
     * @return int
     */
    public function getCouponPackageId(): int
    {
        return $this->couponPackageId;
    }

    /**
     * @param int $couponPackageId
     */
    public function setCouponPackageId(int $couponPackageId): void
    {
        $this->couponPackageId = $couponPackageId;
    }

    /**
     * @return array
     */
    public function getCouponPackageCouponList(): array
    {
        return $this->couponPackageCouponList;
    }

    /**
     * @param array $couponPackageCouponList
     */
    public function setCouponPackageCouponList(array $couponPackageCouponList): void
    {
        $this->couponPackageCouponList = $couponPackageCouponList;
    }

    /**
     * @return array
     */
    public function getCouponPackageInfo(): array
    {
        return $this->couponPackageInfo;
    }

    /**
     * @param array $couponPackageInfo
     */
    public function setCouponPackageInfo(array $couponPackageInfo): void
    {
        $this->couponPackageInfo = $couponPackageInfo;
    }

    /**
     * @return array
     */
    public function getCouponUnablePackageCouponList(): array
    {
        return $this->couponUnablePackageCouponList;
    }

    /**
     * @param array $couponUnablePackageCouponList
     */
    public function setCouponUnablePackageCouponList(array $couponUnablePackageCouponList): void
    {
        $this->couponUnablePackageCouponList = $couponUnablePackageCouponList;
    }

    /**
     * 设置金额为非负数
     *
     * @param $price
     * @return int
     */
    public static function setPriceNotNegative($price): int
    {
        if ((int)$price < 0) {
            return 0;
        }
        return $price;
    }

    /**
     * @return int
     */
    public function getSourceType(): int
    {
        return $this->sourceType;
    }

    /**
     * @param int $sourceType
     */
    public function setSourceType(int $sourceType): void
    {
        $this->sourceType = $sourceType;
    }

    /**
     * @return array
     */
    public function getProductIds(): array
    {
        return $this->productIds;
    }

    /**
     * @param array $productIds
     */
    public function setProductIds(array $productIds): void
    {
        $this->productIds = $productIds;
    }

    /**
     * @return array
     */
    public function getOrderPosDetail(): array
    {
        return $this->orderPosDetail;
    }

    /**
     * @param array $orderPosDetail
     */
    public function setOrderPosDetail(array $orderPosDetail): void
    {
        $this->orderPosDetail = $orderPosDetail;
    }

    /**
     * @return array
     */
    public function getOrderPosTotal(): array
    {
        return $this->orderPosTotal;
    }

    /**
     * @param array $orderPosTotal
     */
    public function setOrderPosTotal(array $orderPosTotal): void
    {
        $this->orderPosTotal = $orderPosTotal;
    }

    /**
     * @param bool $needId
     * @return array
     */
    public function getOrderCustomPaymentList($needId = false): array
    {
        if ($needId) {
            return $this->orderCustomPaymentList;
        } else {
            return array_filter($this->orderCustomPaymentList, function ($item) {
                return empty($item['id']);
            });
        }
    }

    /**
     * @param array $orderCustomPaymentList
     */
    public function setOrderCustomPaymentList(array $orderCustomPaymentList): void
    {
        $this->orderCustomPaymentList = $orderCustomPaymentList;
    }

    /**
     * @return array
     */
    public function getOrderRefundCustomPaymentList(): array
    {
        return $this->orderRefundCustomPaymentList;
    }

    /**
     * @param array $orderRefundCustomPaymentList
     * @param bool $replace
     */
    public function setOrderRefundCustomPaymentList(array $orderRefundCustomPaymentList, bool $replace = false): void
    {
        if (empty($orderRefundCustomPaymentList) && !$replace) {
            return;
        }

        $replace ? $this->orderRefundCustomPaymentList = $orderRefundCustomPaymentList : array_push($this->orderRefundCustomPaymentList,
            $orderRefundCustomPaymentList);

    }

    /**
     * @return array
     */
    public function getOrderUserSidePaymentList(): array
    {
        return $this->orderUserSidePaymentList;
    }

    /**
     * @param array $orderUserSidePaymentList
     * @param bool $replace
     */
    public function setOrderUserSidePaymentList(array $orderUserSidePaymentList, bool $replace = false): void
    {
        if (empty($orderUserSidePaymentList) && !$replace) {
            return;
        }

        $replace ? $this->orderUserSidePaymentList = $orderUserSidePaymentList : array_push($this->orderUserSidePaymentList,
            $orderUserSidePaymentList);
    }

    /**
     * @return array
     */
    public function getOrderRefundPosCouponList(): array
    {
        return $this->orderRefundPosCouponList;
    }

    /**
     * @param array $orderRefundPosCouponList
     * @param bool $replace
     */
    public function setOrderRefundPosCouponList(array $orderRefundPosCouponList, bool $replace = false): void
    {
        if (empty($orderRefundPosCouponList) && !$replace) {
            return;
        }

        $replace ? $this->orderRefundPosCouponList = $orderRefundPosCouponList : array_push($this->orderRefundPosCouponList,
            $orderRefundPosCouponList);

    }

    /**
     * @return string
     */
    public function getOrderTotalShouldPrice(): string
    {
        return $this->orderTotalShouldPrice;
    }

    /**
     * @param string $orderTotalShouldPrice
     */
    public function setOrderTotalShouldPrice(string $orderTotalShouldPrice): void
    {
        $this->orderTotalShouldPrice = $orderTotalShouldPrice;
    }

    /**
     * @return array
     */
    public function getOrderRefundPaymentList(): array
    {
        return $this->orderRefundPaymentList;
    }

    /**
     * @param array $orderRefundPaymentList
     * @param bool $replace
     */
    public function setOrderRefundPaymentList(array $orderRefundPaymentList, bool $replace = false): void
    {
        if (empty($orderRefundPaymentList) && !$replace) {
            return;
        }

        $replace ? $this->orderRefundPaymentList = $orderRefundPaymentList : array_push($this->orderRefundPaymentList,
            $orderRefundPaymentList);

    }

    /**
     * @return array
     */
    public function getOrderPaymentList(): array
    {
        return $this->orderPaymentList;
    }

    /**
     * @return array
     */
    public function getOrderValidPaymentList()
    {
        $data = $this->getOrderPaymentList();
        return array_filter($data, function ($item) {
            return !empty($item['id']) && !$item['isNew'];
        });
    }

    public function getOrderPaymentInfo(string $id)
    {
        $info = [];
        foreach ($this->getOrderPaymentList() as $key => $list) {
            if (empty($list['id'])) {
                continue;
            }
            if ($list['id'] == $id) {
                $info = $list;
                break;
            }
        }

        return $info;
    }

    /**
     * @param array $orderPaymentList
     * @param bool $replace
     */
    public function setOrderPaymentList(array $orderPaymentList, bool $replace = false): void
    {
        if (empty($orderPaymentList) && !$replace) {
            return;
        }

        $replace ? $this->orderPaymentList = $orderPaymentList : array_push($this->orderPaymentList,
            $orderPaymentList);
    }

    public function initPostData($data): OrderCalcService
    {
        $this->postData = $data;
        return $this;
    }

    /**
     * 获取产品信息
     *
     * @return array
     * @throws OrderException
     */
    public function getProductLists(): array
    {
        $productList = [];
        foreach ($this->orderPosDetail as $posDetail) {
            $productList[] = [
                'productId' => $posDetail['productId'],
                'number'    => $posDetail['num'],
                'price'     => $this->getProductField($posDetail['productId'], 'price'),
                'freeNum'   => $posDetail['freeNum'] ?? 0,
                'refundNum' => $posDetail['refundNum'] ?? 0,
                'name'      => $this->getProductField($posDetail['productId'], 'name'),
            ];
        }
        return $productList;
    }

    /**
     * 获取菜品数据
     *
     * @return array
     */
    public function getOrderProductDetail(): array
    {
        return $this->orderProductDetail;
    }


    private static array $loopOrderFilterOrderDetail = [];

    /**
     * 获取符合优惠券的菜品数据
     *
     * @param array $couponCanUseProducts
     * @param array $useProductsList
     * @return array
     */
    public function getOrderDetailWithCouponCanUseProducts(array $couponCanUseProducts, array $useProductsList): array
    {
        $filterProductList = [];

        if (!count($couponCanUseProducts) || !count($useProductsList)) {
            return [];
        }

        if (empty(self::$loopOrderFilterOrderDetail)) {
            self::$loopOrderFilterOrderDetail = $this->orderFilterOrderDetail;
        }

        $loopCouponCanUseProducts = $useProductsList;
        // 找到第一个非赠菜的符合商品Id的菜, 以及数量对的菜
        foreach (self::$loopOrderFilterOrderDetail as &$detail) {
            foreach ($loopCouponCanUseProducts as $key => $product) {
                if ($detail['productId'] == $product['canUseProduct']
                    && $detail['freeNum'] == 0
                    && $detail['refundNum'] == 0
                    && $detail['num'] == $product['productLimit']
                    && empty($detail['isAssociatedCoupon'])
                ) {
                    $filterProductList[]          = $detail;
                    $detail['isAssociatedCoupon'] = true; // 是否关联商品券
                    unset($loopCouponCanUseProducts[$key]);
                }
            }
        }

        if (empty($loopCouponCanUseProducts)) {
            return $filterProductList;
        }

        return [];
    }

    /**
     * 重新计算优惠券抵扣金额
     *
     * @param array $filterProductList
     * @return string
     */
    public function reCalcCouponDeduction(array $filterProductList): string
    {
        $reCalcDeduction = '0';
        if (!count($filterProductList)) {
            return $reCalcDeduction;
        }

        foreach ($filterProductList as $item) {
            $reCalcDeduction = bcadd($reCalcDeduction, $item['payPrice']);
        }

        return $reCalcDeduction;
    }


    public function calcSingleUnit(array $filterProductList, int $num): array
    {
        if ($num == 1) {
            return $filterProductList;
        }

        $listProductNums = [];
        foreach ($filterProductList as $product) {
            $listProductNums[$product['canUseProduct']] = ($listProductNums[$product['canUseProduct']] ?? 0) + 1;
        }


        // 这里 处理数据
        $singleUnit = [];
        foreach ($listProductNums as $key => $productNum) {
            $singleUnitNum    = (int)bcdiv($productNum, $num);
            $singleUnit[$key] = $singleUnitNum;
        }


        $returnData            = [];
        $filterProductListKeys = array_column($filterProductList, null, 'canUseProduct');

        foreach ($singleUnit as $key => $item) {
            for ($i = 1; $i <= $item; $i++) {
                $returnData[] = $filterProductListKeys[$key];
            }
        }

        return $returnData;
    }

}
